每日一包 - pyjwt

364 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

JWT认证简介

在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT构成和工作原理

JWT构成

JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE1OTQ4MDg0NDMsImVtYWlsIjoiIn0.FJqRgUiWdg4momwe18pPpYDnlKKFLi_Z_ifLjk4vGAI

第一部分我们称它为头部(header):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.

第二部分我们称其为载荷(payload):

eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE1OTQ4MDg0NDMsImVtYWlsIjoiIn0

第三部分是签证(signature)

FJqRgUiWdg4momwe18pPpYDnlKKFLi_Z_ifLjk4vGAI

  • header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
   
}

将头部进行base64编码(加密,该加密是可以对称解密的(解码))构成了第一部分

  • payload

荷载是存放有效信息的地方,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

将其进行base64加密,得到JWT的第二部分。

  • signature

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

将这三部分用.连接成一个完整的字符串,构成了最终的jwt.

pyjwt使用

上面对JWT进行了简单的介绍之后,就来说明如何借助pyjwt这个模块生成我们自己的token。

安装

pip install pyjwt

使用 - encode

在上文中对JWT的组成了解之后对我们使用pyjwt非常有帮助,首先来看pyjwt的第一个需要知道如何使用的方法:encode

jwt.encode(
        self,
        payload: Dict[str, Any],
        key: str,
        algorithm: Optional[str] = "HS256",
        headers: Optional[Dict] = None,
        json_encoder: Optional[Type[json.JSONEncoder]] = None,
    ) -> str:
"""
payload: 有效的荷载
key: 加密的盐
algorithm:加密算法
headers: 头部,在jwt源码中头部已经被定义
返回值:token字符串
"""

知道了每个参数的意义之后,可以查看下面示例代码:

import jwt
​
payload = {
        "user": "username",
        "is_admin": True
    }
salt = '加密时的盐'token = jwt.encode(payload=payload, key=salt)
print(token)

使用 - decode

获取token之后,我们需要对其中的数据进行反解,需要用到decode方法,该方法可以将token中的payload部分反解出来。

jwt.decode(jwt , key="" , algorithms=None , options=None , Audience=None , issuer=None , leeway=0) -> Dict[str, Any]:
"""
jwt: token字符串
key:加的盐
algorithms:允许的加密算法,是一个列表
options(dict):扩展的解码和验证选项,默认是None
其余参数皆为可选参数
返回值:payload
"""

再来看示例代码:

token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NDYyMTcxODMsImlhdCI6MTY1NDg1NzE4MywiZGF0YSI6eyJ1c2VyIjoidXNlcm5hbWUiLCJpc19hZG1pbiI6dHJ1ZX19.BY4s7W5GItoZG9jT5ryfpKwEt0R6XRmt7L-1XNiHt64'
res = jwt.decode(token, key=salt, algorithms="HS256")
​
print(res)

上面是签发token的基本使用方式,我们还可以在payload中指定token的过期时间(exp),这也是符合实际应用场景的,也可以指定token的生效时间(nbf),以及token的开始时间,开始时间和生效时间是类似的,下面我们就通过代码演示这三种时间。

payload = {
    'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1),  # 过期时间1分钟
    'iat': datetime.datetime.utcnow(),  # 开始时间
    'nbf': datetime.datetime.utcnow() + datetime.timedelta(minutes=1),  # 生效时间 1分钟后开始生效
    'data': {
        "user": "username",
        "is_admin": True
    }
}
​
token = jwt.encode(payload)  # 在payload中指定了过期时间
payload = jwt.decode(token, algorithms="HS256")

如果decode时超过了过期时间(exp),则会抛出如下异常:

jwt.exceptions.ExpiredSignatureError: Signature has expired

如果当前时间在开始时间(iat)之前,则会抛出如下异常:

jwt.exceptions.InvalidIssuedAtError: Issued At claim (iat) cannot be in the future.

如果token的生效时间(nbf)还没到就进行decode操作,则会抛出如下异常:

jwt.exceptions.ImmatureSignatureError: The token is not yet valid (nbf)

总结

有了JWT之后在我们的项目中就可以不不再使用cookie和session了,从而更加节省服务器资源。