JWT

JWT 简介

JSON WEB TOKEN,通过数字签名的方式,以 JSON 对象为载体,在不同的服务终端之间安全的传输信息。

可以通过 JWT 校验 来验证你的 JWT 生成内容是否正确

JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含 JWT,系统在每次处理用户请求之前,都会进行 JWT 安全校验,通过之后再进行后续处理。

JWT 的组成

JWT 由三部分组成,用 . 进行拼接

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.EKd8mqslm3YqO5cdfIF7mAkP6mdXrazy-hGK_SkJJDc

这三部分分别是:

  1. Header(头部)
  2. payload(负载)
  3. 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'

# 方法1: 使用pyjwt库, pyjwt 会使用默认的header,所以不传数据也能生成正常的 jwt 签名
def get_jwt_token(payload, header=None):
return jwt.encode(payload, SECRET_KEY, algorithm='HS256', headers=header)

# 方法2: 自己实现jwt签名方式
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)
}