- JWT介绍
- JWT的组成部分
- payload
- signature
- jwt认证算法:签发与校验
- 1.
JWT分成3部分:头(header)、体(payload)、签名(signature) - 2.
头(header)和体(payload)是可逆加密,让服务器可以反解出user对象;签名(signature)是不可逆加密,保证整个token的安全性的 - 3.头体签名三部分,都是采用
json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法 - 4.
头(header)中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息 - 5.
体(payload)中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间 - 6.签名中的内容时安全信息:
头的加密结果+体的加密结果+ 服务器不对外公开的安全码进行md5加密
- 1.
- 签发
- 校验
- drf项目的jwt认证开发流程(重点)
- JWT的安装与使用
- 自定义基于jwt的认证类
JWT介绍
简介
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
构成
JWT的本质就是token,它主要有三部分组成,分别是头部(header)、荷载(payload)主题部、以及签证(signature)。
前两部分都是由base64进行编码(可以反解码),后一部分是不可反解的加密,由前两部分base64的结果加密(hash256)后组成。各个部分之间由.来分割
JWT的组成部分
header
在header中,一般携带着2部分信息(声明):
- 声明类型:这里用的是
JWT - 声明加密的算法:通常直接使用
HMACSHA256
除此之外,也可以添加其他声明:比如说,添加公司名称等信息
自定义header,JSON格式:
{"alg": "HS256","typ": "JWT"}
用base64对其进行编码,得到JWT中的header部分:
ewogICAgInR5cGUiOiJKV1QiLAogICAgImVuY29kZV9tZXRob2QiOiJIQVNIMjU2Igp9
payload
荷载部作为JWT三部分中的第二部分,都是存放有效信息。
它可以存放三种类型的有效信息:
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明(建议但不强制使用):
| 荷载部位的key | 描述 |
|---|---|
| iss | JWT签发者(服务端) |
| sub | JWT所面向的用户 |
| aud | 接收JWT的一方 |
| exp | JWT的过期时间,该时间必须大于签发时间 |
| nbf | 再某一时间段之前,该JWT不可用 |
| jti | JWT的唯一身份标识,主要用作一次性token,回避时序攻击 |
公共的声明:
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密。
私有的声明:
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
自定义payload,JSON格式:
{"sub": "1234567890","name": "John Doe","iat": 1516239022}
用base64对其进行编码,得到JWT中的payload部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT的第三部分。
signature = hashlib.sha256()signature.update(header_payload_result)salt = 'llnb'signature.update(salt.encode("utf-8")) # 加盐signature_result = signature.hexdigest() # 获得结果
JWT第三部分结果
b465493b91a918d9d957f61f8222f0f6dcbb58d428977543be33b1ee5067f2
jwt认证算法:签发与校验
1.JWT分成3部分:头(header)、体(payload)、签名(signature)
2.头(header)和体(payload)是可逆加密,让服务器可以反解出user对象;签名(signature)是不可逆加密,保证整个token的安全性的
3.头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4.头(header)中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{"company": "公司信息","type": "JWT","encode_method": "HASH256"...}
5.体(payload)中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{"id": 1024,"sub": "1233211234567","name": "kevin","admin": true}
6.签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{"head": "头的加密字符串","payload": "体的加密字符串","secret_key": "安全码"}
签发
根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
- 用基本信息存储json字典,采用base64算法加密得到
头字符串 - 用关键信息存储json字典,采用base64算法加密得到
体字符串 - 用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到
签名字符串
账号密码就能根据User表得到user对象,形成的三段字符串 用 . 拼接成token返回给前台
校验
根据客户端带token的请求,反解出 user 对象
- 将token按
.拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理 - 第二段 体加密字符串,要反解出
用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且是同一设备来的 - 再用
第一段 + 第二段 + 服务器安全码不可逆md5加密,与第三段签名字符串进行碰撞校验,通过后才能代表第二段校验得到的user对象就是合法的登录用户
drf项目的jwt认证开发流程(重点)
- 用账号密码访问登录接口,登录接口逻辑中调用
签发token 算法,得到token,返回给客户端,客户端自己存到cookies中 校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户
JWT的安装与使用
安装
pip install djangorestframework-jwt
使用
快速使用(默认使用auth的user表)
1.在默认auth的user表中创建一个用户
2.在路由中配置
urlpatterns = [path('login/', obtain_jwt_token),]
3.用postman向这个地址发送post请求,携带用户名、密码,登陆成功就会返回token
4.obtain_jwt_token本质也是一个视图类,继承了APIView
- 通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回
- 如果校验失败,返回错误信息
5.如果用户携带了token,并且配置了JSONWebTokenAuthentication,从request.user就能拿到当前登录用户,如果没有携带,当前登录用户就是匿名用户
6.前端要发送请求,携带jwt,格式必须如下
- 把token放到请求头中,key为:Authorization
- value必须为:jwt
用户登录以后才能访问某个接口
jwt模块内置了认证类,拿过来局部配置就可以
class OrderView(APIView):# 只配它不行,不管是否登录,都能范围,需要搭配一个内置权限类authentication_classes = [JSONWebTokenAuthentication, ]permission_classes = [IsAuthenticated,]def get(self, request):print(request.user.username)return Response('ok')
自定义基于jwt的认证类
自定义用户表签发token
使用
rom rest_framework_jwt.settings import api_settingsjwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLERjwt_encode_handler = api_settings.JWT_ENCODE_HANDLERfrom rest_framework.decorators import actionclass UserView(ViewSet):@action(methods=['POST'],detail=False)def login(self, request):username = request.data.get('username')password = request.data.get('password')user = UserInfo.objects.filter(username=username, password=password).first()if user:# 登录成功---》签发tokenpayload = jwt_payload_handler(user) # 根据当前登录用户获取荷载print(payload)token = jwt_encode_handler(payload) # 根据荷载生成tokenreturn Response({'code': 100, 'msg': '登录成功', 'token': token})else:return Response({'code': 101, 'msg': '用户名或密码错误'})
自定义认证类验证token
使用
from rest_framework.authentication import BaseAuthenticationfrom rest_framework_jwt.settings import api_settingsfrom .models import UserInfofrom rest_framework import exceptionsjwt_decode_handler = api_settings.JWT_DECODE_HANDLERjwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLERclass LoginAuth(BaseAuthentication):def authenticate(self, request):token = request.META.get('HTTP_TOKEN')if not token:raise exceptions.APIException('请先登录')# 验证token是否合法# try:# payload = jwt_decode_handler(token)# except jwt.ExpiredSignature:# msg = 'token过期了'# raise exceptions.AuthenticationFailed(msg)# except jwt.DecodeError:# msg = 'token解码错误'# raise exceptions.AuthenticationFailed(msg)# except jwt.InvalidTokenError:# msg = '解析token未知错误'# raise exceptions.AuthenticationFailed(msg)try:payload = jwt_decode_handler(token)except Exception:raise exceptions.APIException('token错误')user = UserInfo.objects.filter(pk=payload['user_id']).first()return user, token
全局使用
setting.py
REST_FRAMEWORK = {authentication_classes = [JwtAuthentication, ],}
局部使用
views.py
class Mine(APIView):authentication_classes = [JwtAuthentication, ]permission_classes = [IsAuthenticated,]...
局部禁用
class Mine(APIView):authentication_classes = []
控制登录接口返回的数据格式
utils
from homework.serializer import UserReadOnlyModelSerializerdef jwt_response_payload_handler(token, user=None, request=None):return {'code': 200,'username': user.username,'msg': '登录成功','token': token}
settings.py
# 配置文件配置JWT_AUTH = {'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',}
