jwt介绍
在项目开发中,
用户注册或登录登录,需要记录用户的登录状态,或者为用户创建身份认证的凭证。
由于Session认证机制的诸多缺陷,已渐渐不使用Session认证了,
而使用Json Web Token认证机制。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).
该token设计紧凑且安全,特别适用于分布式站点的单点登录(SSO)场景。
JWT的声明一般被用来在身份提供者(客户端)和服务提供者(服务端)间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于身份认证,也可被数据加密传输。
JWT本质就是一段字符串,由三段信息构成的,将这三段信息文本用 .
拼接一起就构成了Jwt token字符串。
就像这样:
eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAiMTUwMTIzNDU1IiwgImlhdCI6ICIxNTAxMDM0NTUiLCAibmFtZSI6ICJ3YW5neGlhb21pbmciLCAiYWRtaW4iOiB0cnVlLCAiYWNjX3B3ZCI6ICJRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjkifQ==.815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b
-
第一部分:header(头部)
-
第二部分:payload(载荷,类似于飞机上承载的物品)
-
第三部分:signature(签证)
01. header
jwt的头部承载两部分信息:
- typ: 声明token类型,这里是jwt ,typ的值也可以是:Bear
- alg: 声明签证的加密算法,通常直接使用 HMAC SHA256
完整的头部就像下面这样的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64编码,构成了jwt的第一部分头部
python代码举例:
import base64, json
header_data = {"typ": "jwt", "alg": "HS256"}
# base64只能对字节类型进行加密
header = base64.b64encode( json.dumps(header_data).encode() ).decode()
print(header) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9
02. payload
载荷就是存放有效信息的地方。
这个名字像是特指飞机上承载的货仓。
这些有效信息包含三个部分:
- 标准声明
- 公共声明
- 私有声明
标准声明指定jwt实现规范中要求的属性。 (官方建议但不强制使用) :
- iss: jwt签发者(一般是服务端的域名)
- sub: jwt所面向的用户(一般为客户端IP)
- aud: 接收jwt的一方
- exp: (时间戳)jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: (时间戳)定义在什么时间之后,该jwt才可以使用
- iat: (时间戳)jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token, 从而回避重放攻击。
公共声明 : 公共的声明可以添加任何的公开信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可直接读取.
私有声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,里面存放的是一些可以在服务端或者客户端通过秘钥进行加密和解密的加密信息。往往采用的RSA非对称加密算法。
举例,定义一个payload载荷信息,demo/jwtdemo.py:
import base64, json, time
if __name__ == '__main__':
# 载荷
iat = int(time.time())
payload_data = {
"sub": "root",
"exp": iat + 3600, # 假设一小时过期
"iat": iat,
"name": "wangxiaoming",
"avatar": "1.png",
"user_id": 1,
"admin": True,
"acc_pwd": "QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9",
}
# 将其进行base64编码,得到JWT的第二部分。
payload = base64.b64encode(json.dumps(payload_data).encode()).decode()
print(payload)
# eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjQ3Nzc0Mjk1LCAiaWF0IjogMTY0Nzc3MDY5NSwgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImF2YXRhciI6ICIxLnBuZyIsICJ1c2VyX2lkIjogMSwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=
03. signature
JWT的第三部分是一个签证信息,用于辨真伪,防篡改。这个签证信息由三部分组成:
- header (base64后的头部)
- payload (base64后的载荷)
- secret(保存在服务端的秘钥字符串,不会提供给客户端的,这样可以保证客户端没有签发token的能力)
举例,定义一个完整的jwt token,demo/jwtdemo.py:
import base64, json, hashlib
if __name__ == '__main__':
"""jwt 头部的生成"""
header_data = {"typ": "jwt", "alg": "HS256"}
header = base64.b64encode( json.dumps(header_data).encode() ).decode()
print(header) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9
"""jwt 载荷的生成"""
payload_data = {
"sub": "root",
"exp": "150123455",
"iat": "150103455",
"name": "wangxiaoming",
"admin": True,
"acc_pwd": "QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9",
}
# 将其进行base64编码,得到JWT的第二部分。
payload = base64.b64encode(json.dumps(payload_data).encode()).decode()
print(payload) # eyJzdWIiOiAicm9vdCIsICJleHAiOiAiMTUwMTIzNDU1IiwgImlhdCI6ICIxNTAxMDM0NTUiLCAibmFtZSI6ICJ3YW5neGlhb21pbmciLCAiYWRtaW4iOiB0cnVlLCAiYWNjX3B3ZCI6ICJRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjkifQ==
# 一般密钥写入配置文件,以下面的方式导入
# from django.conf import settings
# secret = settings.SECRET_KEY
secret = 'django-insecure-hbcv-y9ux0&8qhtkgmh1skvw#v7ru%t(z-#chw#9g5x1r3z=$p'
data = header + payload + secret # 秘钥绝对不能提供给客户端。
HS256 = hashlib.sha256()
HS256.update(data.encode('utf-8'))
signature = HS256.hexdigest()
print(signature) # 815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b
# jwt 最终的生成
token = f"{header}.{payload}.{signature}"
print(token)
# eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAiMTUwMTIzNDU1IiwgImlhdCI6ICIxNTAxMDM0NTUiLCAibmFtZSI6ICJ3YW5neGlhb21pbmciLCAiYWRtaW4iOiB0cnVlLCAiYWNjX3B3ZCI6ICJRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjkifQ==.815ce0e4e15fff813c5c9b66cfc3791c35745349f68530bc862f7f63c9553f4b
注意:
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,
secret就是用来进行jwt的签发和jwt的验证,所以,它就是服务端的私钥,在任何场景都不应该泄露出去。
一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
举例,定义一个完整的jwt token,并认证token,
demo/jwtdemo.py:
import base64, json, hashlib
from datetime import datetime
if __name__ == '__main__':
# 头部生成原理
header_data = {
"typ": "jwt",
"alg": "HS256"
}
# print( json.dumps(header_data).encode() )
# json转成字符串,接着base64编码处理
header = base64.b64encode(json.dumps(header_data).encode()).decode()
print(header) # eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9
# 载荷生成原理
iat = int(datetime.now().timestamp()) # 签发时间
payload_data = {
"sub": "root",
"exp": iat + 3600, # 假设一小时过期
"iat": iat,
"name": "wangxiaoming",
"admin": True,
"acc_pwd": "QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9QiLCJhbGciOiJIUzI1NiJ9",
}
payload = base64.b64encode(json.dumps(payload_data).encode()).decode()
print(payload)
# eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjM2NTk3OTAzLCAiaWF0IjogMTYzNjU5NDMwMywgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=
# 一般密钥写入配置文件,以下面的方式导入
# from django.conf import settings
# secret = settings.SECRET_KEY
secret = 'django-insecure-hbcv-y9ux0&8qhtkgmh1skvw#v7ru%t(z-#chw#9g5x1r3z=$p'
data = header + payload + secret # 秘钥绝对不能提供给客户端。
HS256 = hashlib.sha256()
HS256.update(data.encode('utf-8'))
signature = HS256.hexdigest()
print(signature) # ce46f9d350be6b72287beb4f5f9b1bc4c42fc1a1f8c8db006e9e99fd46961156
# jwt 最终的生成
token = f"{header}.{payload}.{signature}"
print(token)
# eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjM2NTk3OTAzLCAiaWF0IjogMTYzNjU5NDMwMywgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=.ce46f9d350be6b72287beb4f5f9b1bc4c42fc1a1f8c8db006e9e99fd46961156
# 认证环节
token = "eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiAicm9vdCIsICJleHAiOiAxNjM2NTk3OTAzLCAiaWF0IjogMTYzNjU5NDMwMywgIm5hbWUiOiAid2FuZ3hpYW9taW5nIiwgImFkbWluIjogdHJ1ZSwgImFjY19wd2QiOiAiUWlMQ0poYkdjaU9pSklVekkxTmlKOVFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5In0=.ce46f9d350be6b72287beb4f5f9b1bc4c42fc1a1f8c8db006e9e99fd46961156"
# token = "eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9.eyJzdWIiOiJyb290IiwiZXhwIjoxNjMxNTI5MDg4LCJpYXQiOjE2MzE1MjU0ODgsIm5hbWUiOiJ3YW5neGlhb2hvbmciLCJhZG1pbiI6dHJ1ZSwiYWNjX3B3ZCI6IlFpTENKaGJHY2lPaUpJVXpJMU5pSjlRaUxDSmhiR2NpT2lKSVV6STFOaUo5UWlMQ0poYkdjaU9pSklVekkxTmlKOSJ9.b533c5515444c51058557017e433d411379862d91640c8beed6f2617b1da2feb"
header, payload, signature = token.split(".")
# 验证是否过期了
# 先基于base64,接着使用json解码
payload_data = json.loads( base64.b64decode(payload.encode()) )
print(payload_data)
exp = payload_data.get("exp", None)
if exp is None or int(exp) < int(datetime.now().timestamp()):
print("token过期!!!")
else:
print("没有过期")
# 验证token是否有效,是否被篡改
# from django.conf import settings
# secret = settings.SECRET_KEY
secret = 'django-insecure-hbcv-y9ux0&8qhtkgmh1skvw#v7ru%t(z-#chw#9g5x1r3z=$p'
data = header + payload + secret # 秘钥绝对不能提供给客户端。
HS256 = hashlib.sha256()
HS256.update(data.encode('utf-8'))
new_signature = HS256.hexdigest()
if new_signature != signature:
print("认证失败")
else:
print("认证通过")
04. 浏览器解析
在浏览器中,JavaScript有一个简单的函数 atob()
,可以简单地解析jwt字符串。