JWT介绍

简介

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

构成

JWT的本质就是token,它主要有部分组成,分别是头部(header)荷载(payload)主题部、以及签证(signature)

前两部分都是由base64进行编码(可以反解码),后一部分是不可反解的加密,由前两部分base64的结果加密(hash256)后组成。各个部分之间由.来分割

JWT的组成部分

header

header中,一般携带着2部分信息(声明):
  1. 声明类型:这里用的是JWT
  2. 声明加密的算法:通常直接使用 HMAC SHA256

除此之外,也可以添加其他声明:比如说,添加公司名称等信息

自定义headerJSON格式:
  1. {
  2. "alg": "HS256",
  3. "typ": "JWT"
  4. }

base64对其进行编码,得到JWT中的header部分:
  1. ewogICAgInR5cGUiOiJKV1QiLAogICAgImVuY29kZV9tZXRob2QiOiJIQVNIMjU2Igp9

payload

荷载部作为JWT三部分中的第二部分,都是存放有效信息

它可以存放种类型的有效信息:
  1. 标准中注册的声明
  2. 公共的声明
  3. 私有的声明

标准中注册的声明(建议但不强制使用):
荷载部位的key 描述
iss JWT签发者(服务端)
sub JWT所面向的用户
aud 接收JWT的一方
exp JWT的过期时间,该时间必须大于签发时间
nbf 再某一时间段之前,该JWT不可用
jti JWT的唯一身份标识,主要用作一次性token,回避时序攻击

公共的声明:

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

私有的声明:

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

自定义payloadJSON格式:
  1. {
  2. "sub": "1234567890",
  3. "name": "John Doe",
  4. "iat": 1516239022
  5. }

base64对其进行编码,得到JWT中的payload部分:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

signature

JWT的第三部分是一个签证信息,这个签证信息由部分组成
  1. header (base64后的)
  2. payload (base64后的)
  3. secret

这个部分需要base64加密后的headerbase64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了JWT的第三部分。
  1. signature = hashlib.sha256()
  2. signature.update(header_payload_result)
  3. salt = 'llnb'
  4. signature.update(salt.encode("utf-8")) # 加盐
  5. 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采用的加密方式信息
  1. {
  2. "company": "公司信息",
  3. "type": "JWT",
  4. "encode_method": "HASH256"
  5. ...
  6. }

5.(payload)中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
  1. {
  2. "id": 1024,
  3. "sub": "1233211234567",
  4. "name": "kevin",
  5. "admin": true
  6. }

6.签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
  1. {
  2. "head": "头的加密字符串",
  3. "payload": "体的加密字符串",
  4. "secret_key": "安全码"
  5. }

签发

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

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

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

校验

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

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

drf项目的jwt认证开发流程(重点)

  1. 用账号密码访问登录接口,登录接口逻辑中调用 签发token 算法,得到token,返回给客户端,客户端自己存到cookies
  2. 校验token的算法应该写在认证类中(在认证类中调用),全局配置给认证组件,所有视图类请求,都会进行认证校验,所以请求带了token,就会反解出user对象,在视图类中用request.user就能访问登录的用户

JWT的安装与使用

安装

  1. pip install djangorestframework-jwt

使用

快速使用(默认使用auth的user表)

1.在默认authuser表中创建一个用户

2.在路由中配置
  1. urlpatterns = [
  2. path('login/', obtain_jwt_token),
  3. ]

3.用postman向这个地址发送post请求,携带用户名、密码,登陆成功就会返回token

4.obtain_jwt_token本质也是一个视图类继承了APIView
  • 通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回
  • 如果校验失败,返回错误信息

5.如果用户携带了token,并且配置了JSONWebTokenAuthentication,从request.user就能拿到当前登录用户,如果没有携带,当前登录用户就是匿名用户

6.前端要发送请求,携带jwt,格式必须如下
  • 把token放到请求头中,key为:Authorization
  • value必须为:jwt

用户登录以后才能访问某个接口

jwt模块内置了认证类,拿过来局部配置就可以
  1. class OrderView(APIView):
  2. # 只配它不行,不管是否登录,都能范围,需要搭配一个内置权限类
  3. authentication_classes = [JSONWebTokenAuthentication, ]
  4. permission_classes = [IsAuthenticated,]
  5. def get(self, request):
  6. print(request.user.username)
  7. return Response('ok')

自定义基于jwt的认证类

自定义用户表签发token

使用

  1. rom rest_framework_jwt.settings import api_settings
  2. jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
  3. jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
  4. from rest_framework.decorators import action
  5. class UserView(ViewSet):
  6. @action(methods=['POST'],detail=False)
  7. def login(self, request):
  8. username = request.data.get('username')
  9. password = request.data.get('password')
  10. user = UserInfo.objects.filter(username=username, password=password).first()
  11. if user:
  12. # 登录成功---》签发token
  13. payload = jwt_payload_handler(user) # 根据当前登录用户获取荷载
  14. print(payload)
  15. token = jwt_encode_handler(payload) # 根据荷载生成token
  16. return Response({'code': 100, 'msg': '登录成功', 'token': token})
  17. else:
  18. return Response({'code': 101, 'msg': '用户名或密码错误'})

自定义认证类验证token

使用

  1. from rest_framework.authentication import BaseAuthentication
  2. from rest_framework_jwt.settings import api_settings
  3. from .models import UserInfo
  4. from rest_framework import exceptions
  5. jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
  6. jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
  7. class LoginAuth(BaseAuthentication):
  8. def authenticate(self, request):
  9. token = request.META.get('HTTP_TOKEN')
  10. if not token:
  11. raise exceptions.APIException('请先登录')
  12. # 验证token是否合法
  13. # try:
  14. # payload = jwt_decode_handler(token)
  15. # except jwt.ExpiredSignature:
  16. # msg = 'token过期了'
  17. # raise exceptions.AuthenticationFailed(msg)
  18. # except jwt.DecodeError:
  19. # msg = 'token解码错误'
  20. # raise exceptions.AuthenticationFailed(msg)
  21. # except jwt.InvalidTokenError:
  22. # msg = '解析token未知错误'
  23. # raise exceptions.AuthenticationFailed(msg)
  24. try:
  25. payload = jwt_decode_handler(token)
  26. except Exception:
  27. raise exceptions.APIException('token错误')
  28. user = UserInfo.objects.filter(pk=payload['user_id']).first()
  29. return user, token

全局使用

setting.py

  1. REST_FRAMEWORK = {
  2. authentication_classes = [JwtAuthentication, ],
  3. }

局部使用

views.py

  1. class Mine(APIView):
  2. authentication_classes = [JwtAuthentication, ]
  3. permission_classes = [IsAuthenticated,]
  4. ...

局部禁用

  1. class Mine(APIView):
  2. authentication_classes = []

控制登录接口返回的数据格式

utils

  1. from homework.serializer import UserReadOnlyModelSerializer
  2. def jwt_response_payload_handler(token, user=None, request=None):
  3. return {
  4. 'code': 200,
  5. 'username': user.username,
  6. 'msg': '登录成功',
  7. 'token': token
  8. }

settings.py

  1. # 配置文件配置
  2. JWT_AUTH = {
  3. 'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
  4. }