JWT
- 参考文档
http://121.5.151.41/mylesson/Django/%E7%BE%8E%E5%A4%9A%E5%95%86%E5%9F%8E%E9%A1%B9%E7%9B%AE%E8%AF%BE%E4%BB%B6/C02-Users/JWT.html
基于token的鉴权机制
- 基于token的鉴权机制类似于http协议也是无状态的
- 它不需要在服务端去保留用户的认证信息或者会话信息
- 这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了
- 这就为应用的扩展提供了便利
- 流程
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
- 注意事项
- 这个token必须要在每次请求时传递给服务端,它应该保存在请求头里
- 服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了 Access-Control-Allow-Origin: *
JWT长什么样?
- JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT的构成
- 第一部分我们称它为头部(header),
- 第二部分我们称其为载荷(payload, 类似于飞机上承载的物品)
- 第三部分是签证(signature).
-
header: jwt的头部承载两部分信息
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
- 例如下面的json
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload(载荷),是存放有效信息的地方(包含三部分),这个名字像是特指飞机上承载的货品
- 标准中注册的声明
- 公共的声明
- 私有的声明
- 标准中注册的声明 (建议但不强制使用)
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
- 公共声明: 公共的声明可以添加任何的信息
- 一般添加用户的相关信息或其他业务需要的必要信息
- 但不建议添加敏感信息,因为该部分在客户端可解密
- 私有的声明 : 私有声明是提供者和消费者所共同定义的声明
一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
- 定义一个payload,然后将其进行base64加密,得到JWT的第二部分
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
......
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature 是一个签证信息,这个签证信息由三部分组成
- header (base64后的)
- payload (base64后的)
- secret(最关键的部分)
- 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串
然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
# js 代码演示
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); # TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
- 将 header, payload, signature 用"."连接成一个完整字符串,构成了最终的jwt
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
- 注意事项
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的
secret就是用来进行jwt的签发和jwt的验证
所以,它就是你服务端的私钥,在任何场景都不应该流露出去
一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
如何应用: 一般是在请求头里加入Authorization,并加上Bearer标注
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
小结
- 优点
- 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用
- 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
- 它不需要在服务端保存会话信息, 所以它易于应用的扩展
- 安全相关
- 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分
- 保护好secret私钥,该私钥非常重要
- 如果可以,请使用https协议
加密与解密
- 使用工具网址
- BASE64编码解码工具:http://tools.jb51.net/transcoding/base64
- 在线图片转换BASE64工具:http://tools.jb51.net/transcoding/img2base64
- Base64在线编码解码 UTF-8版:http://tools.jb51.net/tools/base64_decode-utf8.php
- Base64在线编码解码 gb2312版:http://tools.jb51.net/tools/base64_decode-gb2312.php
python base64库简单使用
- str类型不能直接加密(没有去找base64库是否直接支持),必须先转换为bytes类型,再加密/解密
import base64
my_string = 'king'
data = my_string.encode('utf-8') # 先转换成bytes
secret_string = base64.b64encode(data) # 加密
print(secret_string) # b'a2luZw=='
b_string = base64.b64decode(secret_string) # 解密
print(b_string) # b'king'
new_data = b_string.decode('utf-8') # 还原回来
print(new_data) # king
- 加密图片并还原demo演示
read_img = open('dondon.png','rb')
read_data = read_img.read() # 获取图片的二进制byte数据
# print(read_data) # b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x0......
read_img.close()
secret_data = base64.b64encode(read_data) # 加密(平时可以保存的密文)
# print(secret_data) # b'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABQODxIPDRQSEBIXFRQYHjIhH......
b_data = base64.b64decode(secret_data) # 解密
print(b_data) # b'\xff\xd8\xff\xe0\x00......
write_img = open('new_dondon.png','wb')
write_img.write(b_data) # 把图片还原回来,保存
write_img.close()