JWT就是 JSON Web Tokens的缩写。
首先说它与session的区别:
- session 存储在服务端占用服务器资源,而 JWT 存储在客户端
- session 存储在 Cookie 中,存在伪造跨站请求伪造攻击的风险
- session 只存在一台服务器上,那么下次请求就必须请求这台服务器,不利于分布式应用
- 存储在客户端的 JWT 比存储在服务端的 session 更具有扩展性
- 。。。。。。。。
- .。。。。。。。。嘿嘿 读者自己找。
JWT 标准的 Token 包含三个部分:
- header(头部)
- payload(数据)
- signature(签名)
三部分一般会用点分隔开,且会用base64编码进行对称加密,所以真正的token例如:
JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTBBJTIwJTIyaXNzJTIyJTNBJTIwJTIycm9uZ2hvbmcubmV0JTIyJTJDJTBBJTIwJTIyZXhwJTIyJTNBJTIwJTIyMTQzODk1NTQ0NSUyMiUyQyUwQSUyMCUyMm5hbWUlMjIlM0ElMjAlMjJob25naG9uZyUyMiUyQyUwQSUyMCUyMmFkbWluJTIyJTNBJTIwdHJ1ZSUwQSU3RA==.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U
注(base64加密一般很多时候最后回事“= ” 这是我之前写小爬的时候的经验)
Header
每一个JWT token里面都有一个header,也就是头部数据。里面的主要内容就是:1.这是什么typ(JWT)/2.使用了什么算法alg(HS256)如果未使用加密可为none
JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=
解密后(通过base64解密):
{"typ":"JWT","alg":"HS256"}
Payload
payload里面是Token的具体内容,这些内容里面有一些标准字段,你也可以添加其他需要的内容。下面是标准字段:
- iss:Issuer,发行者
- sub:Subject,主题
- aud:Audience,观众
- exp:Expiration time,过期时间
- nbf:Not before
- iat:Issued at,发行时间
- jti:JWT ID
比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间这两个标准字段。另外还有两个自定义的字段,一个是 name ,还有一个是 admin 。
{
"iss": "ronghong.net",
"exp": "1438955445",
"name": "honghong",
"admin": true
}
使用 base64url 编码以后就变成了这个样子:
JTdCJTBBJTIwJTIyaXNzJTIyJTNBJTIwJTIycm9uZ2hvbmcubmV0JTIyJTJDJTBBJTIwJTIyZXhwJTIyJTNBJTIwJTIyMTQzODk1NTQ0NSUyMiUyQyUwQSUyMCUyMm5hbWUlMjIlM0ElMjAlMjJob25naG9uZyUyMiUyQyUwQSUyMCUyMmFkbWluJTIyJTNBJTIwdHJ1ZSUwQSU3RA==
Signature
JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。
也就是上面最开始我给的那个字符串,通过“.”分隔开的其中最后一部分的内容
服务端会验证token,如果验证通过就会返回相应的资源,整个流程就是这样:
后续 还会写入 JWT实际web编程的小demo,读者可以等我更新
想提前看到可以点这儿jwt运用
go语言的运用:
jwt.go jwt_test.go:
jwt.go:
package main
import (
// "fmt"
"encoding/json"
"encoding/base64"
"crypto/sha256"
"crypto/hmac"
"encoding/hex"
"strings"
)
// SALT 密钥
const SALT = "secret"
// Header 消息头部
type Header struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
}
// PayLoad
type PayLoad struct {
Sub string `json:"sub"`
Name string `json:"name"`
Admin bool `json:"admin"`
// Expire int `json:"exp"`
}
// JWT 完整版
type JWT struct {
Header `json:"header"`
PayLoad `json:"payload"`
Signature string `json:"signature"`
}
func main(){}
// Encode 将 json 转成符合 JWT 标准的字符串
func (jwt *JWT) Encode() string {
header, err := json.Marshal(jwt.Header)
checkError(err)
headerString := base64.StdEncoding.EncodeToString(header)
payload, err := json.Marshal(jwt.PayLoad)
payloadString := base64.StdEncoding.EncodeToString(payload)
checkError(err)
format := headerString + "." + payloadString
signature := getHmacCode(format)
return format + "." + signature
}
//sha256包实现了SHA224和SHA256哈希算法
/*
func New (1)
func New() hash.Hash
返回一个新的使用SHA256校验算法的hash.Hash接口。
------------------------------------------------------------------------------------------
func New (2)
func New(h func() hash.Hash, key []byte) hash.Hash
New函数返回一个采用hash.Hash作为底层hash接口、key作为密钥的HMAC算法的hash接口。
-------------------------------------------------------------------------------------------
type Hash (3)
type Hash interface {
// 通过嵌入的匿名io.Writer接口的Write方法向hash中添加更多数据,永远不返回错误
io.Writer
// 返回添加b到当前的hash值后的新切片,不会改变底层的hash状态
Sum(b []byte) []byte
// 重设hash为无数据输入的状态
Reset()
// 返回Sum会返回的切片的长度
Size() int
// 返回hash底层的块大小;Write方法可以接受任何大小的数据,
// 但提供的数据是块大小的倍数时效率更高
BlockSize() int
}
Hash是一个被所有hash函数实现的公共接口。
-----------------------------------------------------------------------------------------
func EncodeToString (4)
func EncodeToString(src []byte) string
将数据src编码为字符串s。
*/
func getHmacCode(s string) string {
h := hmac.New(sha256.New, []byte(SALT))//(1) (2)
h.Write([]byte(s))
key := h.Sum(nil)// (3)
return hex.EncodeToString(key)//(4)
}
// Decode 验证 jwt 签名是否正确,并将json内容解析出来
func (jwt *JWT) Decode( code string) bool {
arr := strings.Split(code,".")
if len(arr) != 3 {
return false
}
// 验证签名是否正确
format := arr[0] + "." + arr[1]
signature := getHmacCode(format)
if signature != arr[2] {
return false
}
//逆转解码回去
header, err := base64.StdEncoding.DecodeString(arr[0])
checkError(err)
payload, err := base64.StdEncoding.DecodeString(arr[1])
checkError(err)
json.Unmarshal(header, &jwt.Header)
json.Unmarshal(payload,&jwt.PayLoad)
return true
}
func checkError(err error) {
if err != nil {
panic(err)
}
}
jwt_test.go:
package main
import (
"testing"
)
func Test_Encode(t *testing.T) {
jwt := JWT{}
jwt.Header = Header{"HS256","JWT"}
jwt.PayLoad = PayLoad{"1234567890","John Doe",true}
result := jwt.Encode()
t.Log(result)
}
func Test_Decode(t *testing.T) {
testStr := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.4c9540f793ab33b13670169bdf444c1eb1c37047f18e861981e14e34587b1e04"
jwt := JWT{}
if jwt.Decode(testStr) {
t.Log(jwt)
} else {
t.Error("error json content")
}
}
增强jwt安全性方法:
- 缩短 token 有效时间
- 使用安全系数高的加密算法
- token 不要放在 Cookie 中,有 CSRF 风险
- 使用 HTTPS 加密协议
- 对标准字段 iss、sub、aud、nbf、exp 进行校验
- 使用成熟的开源库,不要手贱造轮子
- 特殊场景下可以把用户的 UA、IP 放进 payload 进行校验(不推荐)
参考理解:
https://www.cnblogs.com/yibutian/p/9507866.html