JSON Web Token (JWT)

互联网服务离不开用户认证,其中,session 是常见的认证方式,session 在服务端保存相关数据,这种模式的问题在于,扩展性不好:单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session

JWT 是一种跨域认证解决方案,这种方案服务器不保存任何 session 数据,所有数据都保存在客户端,每次请求都发回服务器。也就是说,服务器变成无状态了,从而比较容易实现扩展

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户。以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名

JWT 结构

JWT 的结构是三部分,用点号连接,分别是 Header、Payload、Signature

写成一行,如下所示:

1
Header.Payload.Signature

Header 部分是一个 JSON 对象,描述 JWT 的元数据

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

alg 属性表示签名的算法,默认是 HMAC SHA256(HS256)

typ 属性表示这个令牌(token)的类型(type),JWT 令牌统一写为 JWT
最后,上面的 JSON 对象会使用 Base64URL 算法转成字符串

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,还可以在这个部分定义私有字段

1
2
3
4
{
"name": "admin",
"admin": true
}

该 JSON 对象也会使用 Base64URL 算法转成字符串

JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分

Signature

Signature 部分是对前两部分的签名,防止数据篡改

它会使用前面 Header 里面指明的算法,按照下面的公式生成

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用 . 分隔,就可以返回给用户

其中,Base64URL 算法是 Base64 的一种变体,具体规则为:base64 的三个字符 =+/ 由于在 URL 里面有特殊含义,所以要被替换掉:= 被省略,+ 替换为 -/ 替换为 _

使用 JWT

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage

以后,客户端每次与服务器通信,都要带上这个 JWT,可以把它放在 Cookie 里面自动发送,但是这样不能跨域

所以更好的做法是放在 HTTP 请求的头信息 Authorization 字段里面

或者跨域的时候,放在 POST 请求的数据体里面发送

JWT 利用

HS256 模式

使用 jwt_tool.py 进行字典破解

1
2
3
4
# 查看JWT内容信息
python jwt_tool.py [JWT_TOKEN]
# 指定字典破解
python jwt_tool.py [JWT_TOKEN] -C -d dictory.txt

RS256 模式

该模式需要拿到私钥和公钥

利用 private.key 生成签名部分,python脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import jwt
import base64
import json

with open("/private.key") as key_file:
private_key = key_file.read()

encoded_header = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
encoded_payload = "eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE3MjUzMjgwNjZ9"

header = json.loads(base64.urlsafe_b64decode(encoded_header).decode('utf-8'))
payload = json.loads(base64.urlsafe_b64decode(encoded_payload).decode('utf-8'))

token = jwt.encode(
headers=header,
payload=payload,
key=private_key,
algorithm="RS256"

)

print(f"生成的 JWT: {token}")

利用 jwt_tool.py 使用 public.key 验证生成签名有效性

1
python jwt_tool.py [JWT_TOKEN] -V -pk /public.key

参考

[1]. JSON Web Token 入门教程