jwt 的使用(drf版的session)
一、jwt介绍和构成
1、jwt:Json Web Token:web方向的token认证方案
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。
1 2 3 4 5 6 7 8 9 10 11 | # 做会话保持的发展历史 - https: / / www.cnblogs.com / liuqingzheng / p / 8990027.html # jwt:Json Web Token:web方向的token认证方案 # 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证(token串)。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制 # Json web token (JWT), JWT用在咱们前后端做登录认证的,如果登录了,就携带token过来,如果没登录,就不携带---》后端通过验证token的准确性,确定是谁访问我们 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519 ).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 |
2、 JWT的构成三部分
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ |
header
jwt的头部承载两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
- 公司信息
头部的内容如下:
1 2 3 4 | { 'typ' : 'JWT' , 'alg' : 'HS256' } |
payload
载荷就是存放有效信息的地方。
- 当前用户信息,用户名
- 当前用户信息,用户id
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
定义一个payload:
1 2 3 4 5 | { "exp" : "1234567890" , # 时间戳 "name" : "John Doe" , "user_id" : 99 } |
signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret 密钥
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret,组合加密,然后就构成了jwt的第三部分。
1、签发:(登录接口)
1 2 3 4 5 6 7 8 9 10 11 12 | - 登录接口,登录成功,签发token(三段式) - header 用base64编码,暂放{ "company" : "公司信息" ,} - payload 用base64编码,暂放{用户名,用户权限,过期时间} - 签名: md5,把header和payload 都update进md5中 - - - 》生成前面 - - - 》base64编码 - 三段拼接起来 - - - 》用 . 分割 |
2、认证
1 2 3 4 5 6 7 | - 认证(认证类) - 用户携带token过来,认证 - 取出第一部分header - 取出第二部分 payload - 使用之前同样的加密算法(密码),得到新前面 - 跟token的第三部分比较,如果一样,表示没有被窜改,顺利继续往下走,返回两个值 - 如果被篡改了,抛异常 |
三、base64编码
1、编码
1 2 3 4 5 6 7 8 9 | import json import base64 # base64 的编码和解码(字符串) ###编码 d = { 'name' : 'lqz' , 'age' : 19 } d_str = json.dumps(d) res = base64.b64encode(d_str.encode( 'utf-8' )) # 字符串转bytes格式 print (res) # eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOX0= |
2、解码
base64编码字符串长度必须是4的倍数,如果不足,用= 补齐,= 一定不会超过3个
1 2 | res = base64.b64decode( 'eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOX0=' ) print (res) |
1 2 3 4 5 6 7 8 | # 安装 pip install djangorestframework - jwt # 签发:默认使用auth的user表签发--》登录接口--人家帮咱们写了 from rest_framework_jwt.views import obtain_jwt_token # 这就是个登录接口,人家帮你写好了 urlpatterns = [ path( 'login/' , obtain_jwt_token), ] |
补充:新增一个用户
1 | python manage.py createsuperuser |
2、认证
需要配合 IsAuthenticated 模块使用
IsAuthenticated
是 drf 中的一个权限类,用于控制用户是否被授权访问特定的 API 视图(view)或资源。这个权限类的主要作用是确保只有已经通过身份验证的用户可以访问受保护的资源,而未经身份验证的用户将被拒绝访问。
1 2 3 4 5 6 7 8 9 | from rest_framework.viewsets import ModelViewSet from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated class BookView(ModelViewSet): authentication_classes = [JSONWebTokenAuthentication, ] permission_classes = [IsAuthenticated] queryset = Book.objects. all () serializer_class = BookSerializer |
3、访问books的查询接口,没有带token
提供token:
先访问jwt的login接口拿到token值
请求头携带token访问,成功访问
heads 中key=Authorization, value=jwt ‘token’ 注意这里的空格
4、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 自定义认证返回结果 # 写个函数response.py def jwt_response_payload_handler(token, user = None , request = None ): return { 'status' : 100 , 'msg' : '登录成功' , 'token' : token, 'username' : user.username } # 配置文件配置 JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER' : 'app01.response.jwt_response_payload_handler' , } |
五、drf-simplejwt的简单使用
1、simplejwt
是 DRF
的插件,所以安装之前需要先安装DRF
1 2 3 4 5 6 | pip install djangorestframework pip install markdown pip install django - filter # 下面安装 simplejwt pip install djangorestframework - simplejwt |
2、配置settings.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES' : [ 'rest_framework_simplejwt.authentication.JWTAuthentication' , # 使用rest_framework_simplejwt验证身份 'rest_framework.authentication.SessionAuthentication' , 'rest_framework.authentication.BasicAuthentication' ], 'DEFAULT_PERMISSION_CLASSES' : [ 'rest_framework.permissions.IsAuthenticated' # 默认权限为验证用户 ], } from datetime import timedelta # simplejwt配置, 需要导入datetime模块 SIMPLE_JWT = { # token有效时长 'ACCESS_TOKEN_LIFETIME' : timedelta(minutes = 30 ), # token刷新后的有效时间 'REFRESH_TOKEN_LIFETIME' : timedelta(days = 1 ), } |
3、路由 urls.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from django.urls import path, include # 导入 simplejwt 提供的几个验证视图类 from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView, TokenVerifyView ) urlpatterns = [ # Django 后台 path( 'admin/' , admin.site.urls), # DRF 提供的一系列身份认证的接口,用于在页面中认证身份,详情查阅DRF文档 path( 'api-auth/' , include( 'rest_framework.urls' , namespace = 'rest_framework' )), # 获取Token的接口 path( 'api/token/' , TokenObtainPairView.as_view(), name = 'token_obtain_pair' ), # 刷新Token有效期的接口 path( 'api/refresh/' , TokenRefreshView.as_view(), name = 'token_refresh' ), # 验证Token的有效性 path( 'api/token/verify/' , TokenVerifyView.as_view(), name = 'token_verify' ), ] |
4、创建数据库并添加用户
1 | python manage.py createsuperuser |
5、获取Token
进入 http://127.0.0.1:8000/api/token/
, 一切正常的话应该会看到DRF
提供的页面,提示GET方法不被允许
,并且下面有用户名和密码的输入框。
6、获取token
返回的信息包括refresh
和access
两个字段。其中refresh
是用于刷新token
的(每个Token都是有时间限制的,过了时间就生效了),access
是用于后续的请求时验证身份的。
7、验证Token
进入http://127.0.0.1:8000/api/token/verify/
, 下面提示输入Token
, 输入刚刚过去到的access
的值,验证成功。注意,验证成功没有提示信息反悔,只有一个200的响应码
8、刷新Token
进入 http://127.0.0.1:8000/api/refresh/
,下面提示填写refresh
,在里面填写上面获取到的refresh
值,如果填写的正确,则会获取到新的Token
,否则会提示验证失败
9、访问自己的视图接口(如:users信息展示接口)
simplejwt
的身份认证方式为:在请求的Headers
里面里面添加设置参数,名称为:Authorization
, 值是一个固定组成的字符串: Bearer
+空格
+ access
, 例如:Bearer [token值]
。 正确的效果如下
六、
1、
1 2 3 4 5 6 7 8 9 10 11 12 13 | from django.db import models from django.contrib.auth.models import AbstractUser # 继承AbstractUser 直接使用自动签发token # 纯自己写的用户表,需要自己签发 class User(models.Model): username = models.CharField(max_length = 32 ) password = models.CharField(max_length = 32 ) email = models.EmailField(max_length = 32 ) gender = models.IntegerField(choices = (( 1 , '男' ), ( 2 , '女' ), ( 0 , '未知' ))) |
2、views
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from django.shortcuts import render from rest_framework.views import APIView from .models import User from rest_framework.response import Response from rest_framework_jwt.settings import api_settings # drf的配置文件 # from rest_framework_jwt.utils import jwt_payload_handler jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER # rest_framework_jwt.utils.jwt_encode_handler jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER class UserView(APIView): def post( self , request): username = request.data.get( 'username' ) password = request.data.get( 'password' ) user = User.objects. filter (username = username, password = password).first() if user: # 签发token # 1 通过user生成payload---》jwt 提供一个方法(username),传入user,返回payload # payload = jwt_payload_handler(user) payload = { 'username' : 'asdfasdf' , 'exp' : 1694401763 } # 2 生成token---》jwt提供了方法,把payload放入--》token token = jwt_encode_handler(payload) return Response({ 'code' : 101 , 'msg' : '登录成功' , 'token' : token, 'username' : user.username}) else : return Response({ 'code' : 101 , 'msg' : '用户名或密码错误' }) |
3、路由
1 | path( 'login/' , UserView.as_view()), |
七、自定义表写认证类
1、auth.py 自定义认证类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed import jwt from rest_framework_jwt.settings import api_settings from .models import User jwt_decode_handler = api_settings.JWT_DECODE_HANDLER from django.utils.translation import ugettext as _ class JWTLoginAuthentication(BaseAuthentication): def authenticate( self , request): # 1 取出用户传入的token----》带在哪里?后端规定的---》规定放在请求头 token=asfas.asfdas.asdfa token = request.META.get( 'HTTP_TOKEN' ) # 2 验证token-->django-jwt 提供了一个校验的方法---》 jwt_decode_handler 通过传入token传,校验,校验通过返回payload,校验失败抛异常 try : payload = jwt_decode_handler(token) # 通过payload 得到当前用户 # 认证类,配好后,只要有认证的,都会走这里---》每次走这里都会查询一次数据库 # 做个优化? # 1 自己根据payload数据,创建一个用户,有的参数没传,会使用默认值,跟我真实用户还是有区别的 # user=User(id=payload.get('user_id'),username=payload.get('username')) # 2 直接返回用户id,后续 的request.user 都是用户id,如果真正要用的时候,再查 user = User.objects.get(pk = payload.get( 'user_id' )) except jwt.ExpiredSignature as e: # msg = _('Signature has expired.') # _是个函数的别名,这个函数是翻译函数,只要做了国际化,它就是中文 msg = '签名过期' raise AuthenticationFailed(msg) except jwt.DecodeError: msg = 'token错误' raise AuthenticationFailed(msg) except jwt.InvalidTokenError: raise AuthenticationFailed( '不合法的token' ) except Exception: raise AuthenticationFailed( '未知错误,请联系系统管理员' ) return (user, token) # 后续的request.user 都是用户id,如果真正要用的时候,再查 ''' 每次访问需要登录的接口,都会去查数据库 所以咱们可以做优化 1 自己生成一个user对象 2 直接返回user_id,以后用的时候,再查数据库 ''' |
注意:
1 token = request.META.get('HTTP_TOKEN') 从请求头去拿
2 payload = jwt_decode_handler(token) 获取token后,解码用到
api_settings.JWT_DECODE_HANDLER
是一个设置,用于指定JWT(JSON Web Token)的解码处理程序
1 2 | # 看jwt_decode_handler的源码 from rest_framework_jwt.utils import jwt_decode_handler |
3 自己造一个 user对象,省去频繁查询数据库 user=User(id=payload.get('user_id'),username=payload.get('username'))
4 token过期时间:默认5分钟
1 | JWT_EXPIRATION_DELTA': datetime.timedelta(seconds = 300 ), |
2、 views
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from rest_framework.response import Response from rest_framework_jwt.settings import api_settings # from rest_framework_jwt.utils import jwt_payload_handler 看源码 jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER # rest_framework_jwt.utils.jwt_encode_handler jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER from rest_framework.viewsets import GenericViewSet from .serializer import LoginSerializer from rest_framework.decorators import action from .auth import JWTLoginAuthentication ### 复杂的方式,校验逻辑放到序列化类中 class UserView(GenericViewSet): authentication_classes = [JWTLoginAuthentication] # authentication_classes = [] serializer_class = LoginSerializer @action (methods = [ 'POST' ], detail = False ) def login( self , request, * args, * * kwargs): ser = self .get_serializer(data = request.data) if ser.is_valid(): token = ser.context.get( 'token' ) username = ser.context.get( 'username' ) return Response({ 'code' : 100 , 'msg' : 'login successful' , 'token' : token, 'username' : username}) else : return Response({ 'code' : 101 , 'msg' : '用户名和密码错误' }) |
签发写了,认证可以使用drf-jwt中的认证模块
1、用户名+密码 邮箱+密码 手机号+密码 都可以登录
用户名、邮箱、手机号通过re正则去匹配,都以username去取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #1 用户名+密码 邮箱+密码 手机号+密码 都可以登录 username + password email + password phone + password 无论是username,email,phone都以 username形式提交到后端 于是:从username字段中取出来的,可能是用户名,可能是邮箱,可能是密码 - - - 》都能登录成功 #2 auth的user签发 #3 签发,校验用户 逻辑-----》放在序列化类中 # 5 存在一个问题---》已经迁移过表了---》已经存在auth的user表了,如果再去继承AbstractUser,再写用户表,就会出错 - 解决方案:以后尽量不要这么做 - 以后要扩写auth的user表,一开始就要扩写,不要等迁移完之后再扩写 - 删库 - 删迁移文件(不要删__init__.py和migrations文件夹) - 项目app的迁移文件 - django内置app的admin和auth的迁移文件 - 重新迁移 - - 两条命令 - 扩写auth的user表,需要在配置文件配置 ###重要 # 6 创建超级用户--->创建到扩写的表 auth的user--》AuthUser python manage.py createsuperuser |
2、 serializer 序列化类
把校验数据、签发token的逻辑放到序列化类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | from .models import AuthUser from rest_framework import serializers import re from rest_framework.exceptions import ValidationError from rest_framework_jwt.settings import api_settings # drf的配置文件 # from rest_framework_jwt.utils import jwt_payload_handler jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER # rest_framework_jwt.utils.jwt_encode_handler jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER # 序列化类干什么? 只用来反序列化的校验 class LoginSerializer(serializers.ModelSerializer): # username 是表中的字段---》自动映射过来的-->A user with that username already exists # username有字段自己的规则---》唯一 unique---》去数据库查询发现有lqz,之间字段自己规则报错了,不会走到全局钩子 username = serializers.CharField() # 需要重写字段,不重写,字段自己规则过不了 class Meta: model = AuthUser # 千万不要加逗号,程序运行不会报错,使用这个地方的时候,就报错了 fields = [ 'username' , 'password' ] # 在类内部 用 __ 开头表示隐藏 # 我们约定俗称,以 _ 开头的,表示只在类内部使用,不给外部用,但是万一外部要用,之间用既可以了 def _get_user( self , attrs): # 用户名从哪拿? attrs是前端传入,校验过后的数据 {"username": "lqz","password": "lqz12345" } username = attrs.get( 'username' ) password = attrs.get( 'password' ) if re.match(r '^1[3-9][0-9]{9}$' , username): # 说明,用户提交的是手机号+密码 user = AuthUser.objects. filter (phone = username).first() elif re.match(r '^.+@.+$' , username): # (这个邮箱正则,不太准确) 如果是邮箱,说明用户提交的是 邮箱+密码 user = AuthUser.objects. filter (email = username).first() else : user = AuthUser.objects. filter (username = username).first() if user and user.check_password(password): return user else : # 在全局钩子中,只要校验不通过,就抛异常 # 不是return raise ValidationError( '用户名或密码错误' ) def _get_token( self , user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token def validate( self , attrs): # 全局钩子 # 1 校验用户 user = self ._get_user(attrs) # 如果返回user,说明,用户名密码对了,如果没走到这里,说明抛异常,抛异常说明用户名密码错误 # 2签发token token = self ._get_token(user) # 3 放在这里面 self 是序列化类的对象 ,context 是空字典,它是 视图类和序列化类之间沟通的桥梁 self .context[ 'token' ] = token self .context[ 'username' ] = user.username # 4 返回校验过后的数据 return attrs |
补充:make_password 和 check_password
make_password: 使得明文密码,加密之后以密文进行存储,且相同的密码密文不一样。
1 | from django.contrib.auth.hashers import make_password, check_password |
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from django.contrib.auth.hashers import make_password class UserView(GenericViewSet): serializer_class = LoginSerializer @action (methods = [ 'POST' ], detail = False ) def register( self , request, * args, * * kwargs): username = request.data.get( 'username' ) password = request.data.get( 'password' ) password_salt = make_password(password) res = User.objects.update_or_create(username = username, password = password_salt) if res: return Response({ 'code' : 200 , 'msg' : 'Registration successful' }) else : return Response({ 'code' : 101 , 'msg' : 'Registration failed' }) @action (methods = [ 'POST' ], detail = False ) def login( self , request, * args, * * kwargs): ser = self .get_serializer(data = request.data) if ser.is_valid(): token = ser.context.get( 'token' ) username = ser.context.get( 'username' ) return Response({ 'code' : 100 , 'msg' : 'login successful' , 'token' : token, 'username' : username}) else : return Response({ 'code' : 101 , 'msg' : '用户名和密码错误' }) |
补充:将token存在浏览器中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | if ser.is_valid(): token = ser.context.get( 'token' ) username = ser.context.get( 'username' ) # 创建一个HTTP响应 response = Response({ 'code' : 100 , 'msg' : 'login successful' , 'token' : token, 'username' : username}) # 在Cookie中设置JWT令牌 response.set_cookie( 'jwt_token' , token, expires = settings.JWT_AUTH[ 'JWT_EXPIRATION_DELTA' ]) return response else : return Response({ 'code' : 101 , 'msg' : '用户名和密码错误' }) ####setting from rest_framework_jwt.settings import api_settings # 设置JWT令牌的过期时间 import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA' : datetime.timedelta(hours = 1 ), # 设置为1小时,可以根据需要进行调整 } |
serializer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | from django.contrib.auth.hashers import check_password class LoginSerializer(serializers.ModelSerializer): username = serializers.CharField() class Meta: model = User fields = [ 'username' , 'password' ] def _get_user( self , attrs): username = attrs.get( 'username' ) password = attrs.get( 'password' ) if re.match(r '^1[3-9][0-9]{9}$' , username): # user = AuthUser.objects.filter(username=username).first() user = User.objects. filter (username = username).first() elif re.match(r '^.+@.+$' , username): user = User.objects. filter (username = username).first() else : user = User.objects. filter (username = username).first() if user and check_password(password, user.password): # if user : return user else : raise ValidationError( '用户名或密码错误' ) def _get_token( self , user): payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) return token def validate( self , attrs): # 全局校验 # 1 校验用户 user = self ._get_user(attrs) # 签发token token = self ._get_token(user) self .context[ 'token' ] = token self .context[ 'username' ] = user.username # 返回校验过后的数据 return attrs |
3、views
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | from rest_framework_jwt.views import obtain_jwt_token from rest_framework.viewsets import GenericViewSet from .serializer import LoginSerializer from rest_framework.decorators import action from rest_framework.response import Response from rest_framework_jwt.settings import api_settings # from rest_framework_jwt.utils import jwt_payload_handler jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER # rest_framework_jwt.utils.jwt_encode_handler jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER ## 复杂方式---》校验逻辑--》放在序列化类中 class UserView(GenericViewSet): # queryset = 可以不写 serializer_class = LoginSerializer @action (methods = [ 'POST' ], detail = False ) def login( self , request, * args, * * kwargs): # 拿到前端传入的用户名和密码,得到一个序列化类对象 ser = self .get_serializer(data = request.data) if ser.is_valid(): # 字段自己(校验不过,因为username在数据库中有你传入的这个名字了),局部钩子,全局钩子---》需要在序列化类中写 # 校验完,并且签发完token了 # token从 序列化类的对象 取出来 # username从 序列化类的对象 取出来 # 现在不明白如何取出来的,假设取出来是对的---》因为在全局钩子中放入到了context中 token = ser.context.get( 'token' ) username = ser.context.get( 'username' ) return Response({ 'code' : 100 , 'msg' : '登录成功' , 'token' : token, 'username' : username}) else : return Response({ 'code' : 101 , 'msg' : '用户名密码错误' }) |
4、路由 urls
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from django.contrib import admin from django.urls import path, include from app01.views import UserView from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register( 'users' , UserView, 'users' ) urlpatterns = [ path( 'admin/' , admin.site.urls), ] urlpatterns + = router.urls |
5、post 访问
九、双token认证
1、什么是双token?
双Token机制,主要的是Access Token和 Refresh Token,这两个token都将在用户身份认证成功时生成。
Access Token:Access Token是用户进行API请求的认证令牌。它有一个较短的有效时间,例如5分钟、1小时等。这是为了安全考虑,一旦这个token被攻击者窃取,攻击者能够利用这个token的时间就很有限。
Refresh Token:Refresh Token用来在Access Token过期后申请新的Access Token。Refresh Token的有效时间一般会设置得比较长,例如24小时、7天或30天。当Access Token过期,但Refresh Token没有过期时,用户可以用Refresh Token来申请新的Access Token,而不用重新登录。这增加了用户使用API的便利性。 这个机制的好处是,即使Access Token被窃取,攻击者也只有非常有限的时间可以使用这个token,而Refresh Token则一般不会用来直接请求API,所以被窃取的可能性较小。这样可以很大程度增加系统的安全性。
十、
1、签发的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | # 1 入口是:obtain_jwt_token---》from rest_framework_jwt.views import obtain_jwt_token # 2 obtain_jwt_token本质是:ObtainJSONWebToken.as_view() # 3 看ObtainJSONWebToken视图类 - 前端post请求,提交了用户名密码,就能签发token - 去ObtainJSONWebToken中找 - - - 》post # 4 发现没有 class ObtainJSONWebToken(JSONWebTokenAPIView): serializer_class = JSONWebTokenSerializer #就是它 # 5 去父类中找JSONWebTokenAPIView class JSONWebTokenAPIView(APIView): def post( self , request, * args, * * kwargs): #serializer = self.get_serializer(data=request.data) # 配置的序列化类 serializer = JSONWebTokenSerializer(data = request.data) if serializer.is_valid(): # 字段自己,局部,全局 user = serializer. object .get( 'user' ) token = serializer. object .get( 'token' ) # 定制返回格式--》如果咱们写了,走自己的 response_data = jwt_response_payload_handler(token, user, request) response = Response(response_data) return response return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST) # 6 校验用户名密码和生成token,写在了序列化类的--》全局钩子validate中--》JSONWebTokenSerializer class JSONWebTokenSerializer(Serializer): def validate( self , attrs): credentials = { 'username' : attrs.get( "username" ), 'password' : attrs.get( 'password' ) } if all (credentials.values()): # credentials字典的value必须不能为空 # authenticate django的auth,通过用户名和明文密码校验用户是否存的函数:authenticate # User.objects.filter(username='lqz',password='123').first() # user = authenticate(username='lqz',password='123') user = authenticate( * * credentials) if user: # 登录成功,前token if not user.is_active: # 判断用户是否锁定 msg = _( 'User account is disabled.' ) raise serializers.ValidationError(msg) payload = jwt_payload_handler(user) return { 'token' : jwt_encode_handler(payload), 'user' : user } else : msg = _( 'Unable to log in with provided credentials.' ) raise serializers.ValidationError(msg) else : msg = _( 'Must include "{username_field}" and "password".' ) msg = msg. format (username_field = self .username_field) raise serializers.ValidationError(msg) # 总结 1 前端携带用户名密码到后端 - - - 》执行后端你的post方法 - - - 》后端生成一个序列化类的对象 - - - 》执行校验 - - - 》走了全局钩子 - - 》全局钩子中通过用户名密码获取用户(如果获取不到抛异常) - - - 》获取到后签发token - - - 》签发完返回 - - - - 》在视图类中 - - - 》取出来,返回给前端 |
2、认证的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | # 1 从哪开始看---》认证类JSONWebTokenAuthentication # 2 JSONWebTokenAuthentication 的 class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication): def get_jwt_value( self , request): auth = get_authorization_header(request).split() auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower() if not auth: if api_settings.JWT_AUTH_COOKIE: return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE) return None if smart_text(auth[ 0 ].lower()) ! = auth_header_prefix: return None if len (auth) = = 1 : msg = _( 'Invalid Authorization header. No credentials provided.' ) raise exceptions.AuthenticationFailed(msg) elif len (auth) > 2 : msg = _( 'Invalid Authorization header. Credentials string ' 'should not contain spaces.' ) raise exceptions.AuthenticationFailed(msg) return auth[ 1 ] # 2 父类中BaseJSONWebTokenAuthentication的authenticate def authenticate( self , request): # 拿出前端传入的token,可能前端没传,就是None jwt_value = self .get_jwt_value(request) if jwt_value is None : # None就不管了,认证类继续往后走,相当于没有认证,于是咱们才需要权限类 return None try : payload = jwt_decode_handler(jwt_value) except jwt.ExpiredSignature: msg = _( 'Signature has expired.' ) raise exceptions.AuthenticationFailed(msg) except jwt.DecodeError: msg = _( 'Error decoding signature.' ) raise exceptions.AuthenticationFailed(msg) except jwt.InvalidTokenError: raise exceptions.AuthenticationFailed() user = self .authenticate_credentials(payload) return (user, jwt_value) # 总结:逻辑,跟咱们自定义的一模一样 - 获取token复杂一点,有个前端 |