DRF JWT认证(一)

DRF JWT认证(一) 为什么要使用JWT认证?构成和原理又是什么?怎么还有Base64的事?我都写了

DRF JWT认证(一)

image

JWT认证

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

Json web token (JWT),token是一种认证机制,用在web开发方向,叫JWT

为什么使用JWT 认证?

我们想记录用户注册或者登录后的状态,或者创建身份认证的凭证,可以使用Session认证机制,或者我们现在所要说的JWT 认证机制,那么为什么有了Session认证机制还要使用JWT??

Session机制

image

image

JWT机制

image

image

图解后,可知如果登录用户很多,需要在后端存很多数据,频繁查询数据库,导致效率低,JWT就可以使我们可以不在服务端存数据,又够保证数据安全,在客户端存数据 —-> token认证机制

下面我们了解一下JWT的构成和工作原理

构成和工作原理

JWT的构成

JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。比如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

1. header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存

第一段头部承载的信息:

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

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

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

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. payload

第二段是载荷,载荷就是存放有效信息的地方,是JWT的主体内容部分,也是一个JSON对象,承载的信息:

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

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

除以上标准注册声明字段外,我们还可以自定义字段,一般会把包含用户信息的数据放到payload中,如下例:

{
  "sub": "1234567890",
  "name": "HammerZe",
  "admin": true
}

注意:虽然说用户信息数据可以存放到payload中,但是默认情况下JWT是未加密的,Base64算法也只是编码并不会提供安全的加密算法,一般程序员拿到Base64编码的字符串都可以解码出内容,所以不要存隐私信息,比如密码,防止泄露,存一些非敏感信息

3. signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名

HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)

简单的说第三段是签证信息,这个签证信息由三部分组成:

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

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

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。

总结

注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:

header和payload可以直接利用base64解码出原文,从header中获取哈希签名的算法,从payload中获取有效数据

signature由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。服务端获取header中的加密算法之后,利用该算法加上secretKey对header、payload进行加密,比对加密后的数据和客户端发送过来的是否一致。注意secretKey只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于MD5类型的摘要加密算法,secretKey实际上代表的是盐值

本质原理

JWT认证算法:签发与校验

"""
1)jwt分三段式:头.体.签名 (head.payload.sgin)
2)头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3)头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
	"company": "公司信息",
	...
}
5)体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
	"user_id": 1,
	...
}
6)签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
	"head": "头的加密字符串",
	"payload": "体的加密字符串",
	"secret_key": "安全码"
}
"""

签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token

"""
1)用基本信息存储json字典,采用base64算法加密得到 头字符串
2)用关键信息存储json字典,采用base64算法加密得到 体字符串
3)用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串

账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""

校验:根据客户端带token的请求 反解出 user 对象

"""
1)将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2)第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3)再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""

JWT的种类

其实JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息,JWT的具体实现可以分为以下几种:

  • nonsecure JWT:未经过签名,不安全的JWT
  • JWS:经过签名的JWT
  • JWEpayload部分经过加密的JWT

nonsecure JWT

未经过签名,不安全的JWT。其header部分没有指定签名算法

{
  "alg": "none",
  "typ": "JWT"
}

并且也没有Signature部分

JWS

JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。加密的算法一般有2类:

  • 对称加密:secretKey指加密密钥,可以生成签名与验签
  • 非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)

JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

  • HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
  • RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
  • ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)

base64编码与解码

import base64
import json

dic_info={
  "sub": "1234567890",
  "name": "HammerZe",
  "admin": True
}

'''base64编码'''
# 必须是bytes类型
s = json.dumps(dic_info).encode('utf8')
enbase64_str = base64.b64encode(s)
print(enbase64_str)
# b'eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'

'''base64解码'''
en_res = b'eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'

debase64_str = base64.b64decode(en_res).decode('utf8')
print(base64.b64decode(en_res),type(base64.b64decode(en_res)))
# b'{"sub": "1234567890", "name": "HammerZe", "admin": true}' <class 'bytes'>
print(debase64_str)
# {"sub": "1234567890", "name": "HammerZe", "admin": true}

注意

  • base64长度必须是4的倍速,如果不够就使用=补齐,

哪些情景需求base64?建议参考下博文

为什么要使用base64编码,有哪些情景需求? – 知乎 (zhihu.com)