JWT 简介
JSON WEB TOKEN,通过数字签名的方式,以 JSON 对象为载体,在不同的服务终端之间安全的传输信息。
可以通过 JWT 校验 来验证你的 JWT 生成内容是否正确
JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含 JWT,系统在每次处理用户请求之前,都会进行 JWT 安全校验,通过之后再进行后续处理。
JWT 的组成
JWT 由三部分组成,用 .
进行拼接
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.EKd8mqslm3YqO5cdfIF7mAkP6mdXrazy-hGK_SkJJDc
这三部分分别是:
- Header(头部)
- payload(负载)
- signature(签名)
Header 是一个 JSON 对象主要是用来描述 JWT 的元数据,通常内容如下所示
1 2 3 4
| { "typ": "JWT", "alg": "HS256" }
|
typ
- 用来表示 token 的类型,JWT 令牌统一写为JWT
alg
- 签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256)
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据
1 2 3 4 5
| { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, }
|
官方定义了7个字段,但不是强制的,用户可以自由定义其他字段进行使用
Payload 字段 |
说明 |
iss |
issuer,表示令牌的签发者; |
exp |
expiration,表示令牌的过期时间,为 UTC 时间戳; |
iat |
issued at, 表示令牌的签发时间,为 UTC 时间戳; |
jti |
jwt id, 表示令牌的唯一标示符,可以用于防止重放攻击; |
sub |
subject,表示令牌的主题; |
aud |
audience,表示令牌的受众; |
nbf |
not before,表示令牌的生效时间; |
Signature
签名,需要提前设定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
1 2 3 4
| HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
|
base64 转变为 base64Url 的方法
JWT 使用
如果将数据直接放在 Cookies ,会导致无法跨域请求,所以最好的做法是放在 HTTP 请求的头信息 Authorization
字段里面。
1
| Authorization: Bearer <token>
|
另一个做法是直接将 JWT 数据放在 POST 请求体数据中
JWT 生成代码
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import base64 import json import hashlib import hmac
import jwt import ujson
SECRET_KEY = 'abcde'
def get_jwt_token(payload, header=None): return jwt.encode(payload, SECRET_KEY, algorithm='HS256', headers=header)
def get_jwt_sign(payload, header): payload = base64.b64encode(ujson.dumps(payload).encode('utf-8')).decode('utf-8') header = base64.b64encode(ujson.dumps(header).encode('utf-8')).decode('utf-8') unsign_str = header + '.' + payload unsign_str = unsign_str.replace('=', '').replace('+', '-').replace('/', '_') sign = hmac.new(SECRET_KEY.encode('utf-8'), unsign_str.encode('utf-8'), hashlib.sha256).digest() sign = base64.b64encode(sign).decode('utf-8') sign = sign.replace('=', '').replace('+', '-').replace('/', '_') return unsign_str + '.' + sign
if __name__ == '__main__': header = { "alg": "HS256", "typ": "JWT" } payload = { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, } print(get_jwt_token(payload, header)) print(get_jwt_sign(payload, header))
|
Go
采用 golang-jwt 库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package main
import ( "fmt" "time"
"github.com/golang-jwt/jwt/v4" )
type CustomClaims struct { Subject string `json:"sub,omitempty"` Name string `json:"name,omitempty"` IssuedAt *jwt.NumericDate `json:"iat,omitempty"` jwt.RegisteredClaims }
func CreateJWTToken(payload *CustomClaims, header map[string]interface{}, secret []byte) (string, error) {
token := &jwt.Token{ Header: header, Claims: payload, Method: jwt.SigningMethodHS256, } tokenString, err := token.SignedString(secret) if err != nil { return "", err } return tokenString, nil }
func main() { claims := &CustomClaims{} claims.Subject = "1234567890" claims.Name = "John Doe" claims.IssuedAt = &jwt.NumericDate{ Time: time.Unix(1516239022, 0), } header := map[string]interface{}{ "alg": "HS256", "typ": "JWT", } jwtString, err := CreateJWTToken(claims, header, []byte("abcde")) if err != nil { panic(err) } fmt.Println(jwtString) }
|
手写 jwt 生成代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package main
import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" )
type Payload struct { Subject string `json:"sub,omitempty"` Name string `json:"name,omitempty"` IssuedAt int64 `json:"iat,omitempty"` }
type Header struct { Algorithm string `json:"alg,omitempty"` Type string `json:"typ,omitempty"` }
func CreateJWTBySelf(payload *Payload, header *Header, secret []byte) (string, error) { payloadStr, err := json.Marshal(payload) if err != nil { return "", err }
headerStr, err := json.Marshal(header) if err != nil { return "", err }
unsignedStr := Base64UrlEncode(headerStr) + "." + Base64UrlEncode(payloadStr) sign := HmacSha256(unsignedStr, secret) signStr := Base64UrlEncode(sign) jwt := unsignedStr + "." + signStr return jwt, nil
}
func Base64UrlEncode(str []byte) string { return base64.RawURLEncoding.EncodeToString(str) }
func HmacSha256(message string, secret []byte) []byte { h := hmac.New(sha256.New, secret) h.Write([]byte(message)) sum := h.Sum(nil) return sum }
func main() { payload := &Payload{ Subject: "1234567890", Name: "John Doe", IssuedAt: 1516239022, } header := &Header{ Algorithm: "HS256", Type: "JWT", } secret := []byte("abcde")
jwtString, err := CreateJWTBySelf(payload, header, secret) if err != nil { panic(err) } fmt.Println(jwtString) }
|