9) drf JWT 认证 签发与校验token 多方式登陆 自定义认证规则反爬 admin密文显示

一 、认证方法比较

1.认证规则图

django 前后端不分离 csrf认证

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

drf 前后端分离 禁用csrf

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

2. 认证规则演变图

数据库session认证:低效

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

缓存认证:高效

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

jwt认证:高效

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

3. 认证比较

"""
1)session存储token,需要数据库参与,耗服务器资源、低效
2)缓存存token,需要缓存参与,高效,不易集群
3)客户端存token,服务器存签发与交易token的算法,高效,易集群
"""
缓存认证: 不易并发 集群开发时贼难受

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

jwt认证:易并发,利集群

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

二、JWT认证

1.优点与格式

优点
"""
1) 服务器不要存储token,token交给每一个客户端自己存储,服务器压力小
2)服务器存储的是 签发和校验token的 两段算法,签发认证的效率高
3)算法完成各集群服务器同步成本低,路由项目完成集群部署(适应高并发)
"""
格式
"""
1) jwt token采用三段式:头部.载荷.签名
2)每一部分都是一个json字典加密形参的字符串
3)头部和载荷采用的是base64可逆加密(前台后台都可以解密)
4)签名采用hash256不可逆加密(后台校验采用碰撞校验)
5)各部分字典的内容:
头部:基础信息 - 公司信息、项目组信息、可逆加密采用的算法
载荷:有用但非私密的信息 - 用户可公开信息、过期时间
签名:头部+载荷+秘钥 不可逆加密后的结果
注:服务器jwt签名加密秘钥一定不能泄露 签发token:固定的头部信息加密.当前的登陆用户与过期时间加密.头部+载荷+秘钥生成不可逆加密
校验token:头部可校验也可以不校验,载荷校验出用户与过期时间,头部+载荷+秘钥完成碰撞检测校验token是否被篡改
"""

2. drf-jwt插件安装使用

官网

官网稍微难用,可以选择不同语言不同版本的jwt,官网也已经推出了使用文档

https://github.com/jpadilla/django-rest-framework-jwt
安装

相当于一个模块,只需要下载导入就可使用,不需要注册,不和数据库打交道,models为空

>: pip3 install djangorestframework-jwt
登录 - 自动签发token   路由固定写法:
api/urls.py
# ObtainJSONWebToken视图类就是通过username和password得到user对象然后签发token
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token
urlpatterns = [
# url(r'^jogin/$', ObtainJSONWebToken.as_view()),
url(r'^jogin/$', obtain_jwt_token), # 和上面的路由等效,obtain_jwt_token内部代表的东西和上面是一样的
]

"""
obtain_jwt_token = ObtainJSONWebToken.as_view()
refresh_jwt_token = RefreshJSONWebToken.as_view()
verify_jwt_token = VerifyJSONWebToken.as_view()
"""
认证 - 基于自动签发   校验token:
局部配置drf-jwt的认证类 JSONWebTokenAuthentication
from rest_framework.views import APIView
from utils.response import APIResponse
# 必须登录后才能访问 - 通过了认证权限组件
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class UserDetail(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 局部配置认证类,jwt-token校验request.user
permission_classes = [IsAuthenticated] # 结合权限组件筛选掉游客
def get(self, request, *args, **kwargs):
return APIResponse(results={'username': request.user.username})
路由与接口测试
# 路由
url(r'^user/detail/$', views.UserDetail.as_view()),

# 接口:/api/user/detail/
# 认证信息:必须在请求头的 Authorization 中携带 "jwt 后台签发的token" 格式的认证字符串

三、签发token

源码入口

# 前提:给一个局部禁用了所有 认证与权限 的视图类(第一次登录啥都没有不能搜身)发送用户信息得到token,其实就是登录接口

# 1)rest_framework_jwt.views.ObtainJSONWebToken 的 父类 JSONWebTokenAPIView 的 post 方法
# 接受有username、password的post请求
# 2)post方法将请求数据交给 rest_framework_jwt.serializer.JSONWebTokenSerializer 处理
# 完成数据的校验,会走序列化类的 全局钩子校验规则,校验得到登录用户并签发token存储在序列化对象中

核心源码:

rest_framework_jwt.serializer.JSONWebTokenSerializer的validate(self, attrs)方法

def validate(self, attrs):
# 账号密码字典
credentials = {
self.username_field: attrs.get(self.username_field),
'password': attrs.get('password')
}
if all(credentials.values()):
# 签发token第1步:用账号密码得到user对象
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
# 签发token第2步:通过user得到payload,payload包含着用户信息与过期时间
payload = jwt_payload_handler(user)
# 在视图类中,可以通过 序列化对象.object.get('user'或者'token') 拿到user和token
return {
# 签发token第3步:通过payload签发出token
'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)

签发核心源码

手动签发token逻辑

# 1)通过username、password得到user对象
# 2)通过user对象生成payload: jwt_payload_handler(user) => payload
# from rest_framework_jwt.serializers import jwt_payload_handler
# 3)通过payload签发token: jwt_encode_handler(payload) => token
# from rest_framework_jwt.serializers import jwt_encode_handler

四、校验token

源码入口

# 前提:访问一个配置了jwt认证规则的视图类,就需要提交认证字符串token,在认证类中完成token的校验

# 1)rest_framework_jwt.authentication.JSONWebTokenAuthentication 的 父类 BaseJSONWebTokenAuthentication 的 authenticate 方法
# 请求头拿认证信息jwt-token => 通过反爬小规则确定有用的token => payload => user

核心源码:

rest_framework_jwt.authentication.BaseJSONWebTokenAuthentication的authenticate(self, request)方法

def authenticate(self, request):
# 带有反爬小规则的获取token:前台必须按 "jwt token字符串" 方式提交 # 校验user第1步:从请求头 HTTP_AUTHORIZATION 中拿token,并提取
jwt_value = self.get_jwt_value(request)
# 游客
if jwt_value is None:
return None
# 校验/1
try:
# 校验user第2步:token => payload
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第3步:payload => user
user = self.authenticate_credentials(payload)

return (user, jwt_value)

手动校验token逻辑

# 1)从请求头中获取token
# 2)根据token解析出payload:jwt_decode_handler(token) => payloay
# from rest_framework_jwt.authentication import jwt_decode_handler
# 3)根据payload解析出user:self.authenticate_credentials(payload) => user
# 继承drf-jwt的BaseJSONWebTokenAuthentication,拿到父级的authenticate_credentials方法

五、自定义签发 和  自定义校验

setting
# 自定义 drf-jwt 配置
import datetime
JWT_AUTH = {
# user => payload
'JWT_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_payload_handler',
# payload => token
'JWT_ENCODE_HANDLER':
'rest_framework_jwt.utils.jwt_encode_handler',
# token => payload
'JWT_DECODE_HANDLER':
'rest_framework_jwt.utils.jwt_decode_handler',
# token过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
# token刷新的过期时间
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
# 反爬小措施前缀
'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

jwt配置

1. 案例:实现多方式登陆  签发token

models.py
from django.db import models

from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
mobile = models.CharField(max_length=11, unique=True)

class Meta:
db_table = 'api_user'
verbose_name = '用户表'
verbose_name_plural = verbose_name

def __str__(self):
return self.username
serializers.py 签发
# 1) 前台提交多种登录信息(账号/手机号/邮箱)都采用一个key,所以后台可以自定义反序列化字段进行对应
# 2) 序列化类要处理序列化与反序列化,要在fields中设置model绑定的Model类所有使用到的字段,序列化和反序列化字段都放进去
# 3) 区分序列化字段与反序列化字段 read_only | write_only
# 4) 在自定义校验规则中(局部钩子、全局钩子)校验数据是否合法、确定登录的用户、根据用户签发token
# 5) 将登录的用户与签发的token保存在序列化类对象中
from rest_framework import serializers
from . import models
import re

# 拿到前台token的两个函数: user => payload => token

# from rest_framework_jwt.settings import api_settings
# jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

# 和上面三句话效果一样
from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler

class UserModelSerializer(serializers.ModelSerializer):
# 自定义反序列字段:一定要设置write_only,只参与反序列化,不会与model类字段映射,这里不设置在extra_kwargs中设置应该也可以
# 自定义字段一定不要与models中字段重名
usr = serializers.CharField(write_only=True)
pwd = serializers.CharField(write_only=True)
class Meta:
model = models.User
fields = ['usr', 'pwd', 'username', 'mobile', 'email'] # 序列化和反序列化字段所有字段
# 系统校验规则
extra_kwargs = {
'username': {
'read_only': True
},
'mobile': {
'read_only': True
},
'email': {
'read_only': True
},
}

def validate(self, attrs): # 全局钩子
usr = attrs.get('usr')
pwd = attrs.get('pwd')

# 多方式登录:各分支处理得到该方式下对应的用户
if re.match(r'.+@.+', usr): # 如果是邮箱登录
user_query = models.User.objects.filter(email=usr)
elif re.match(r'1[3-9][0-9]{9}', usr): # 如果是手机号
user_query = models.User.objects.filter(mobile=usr)
else: # 如果是用户名
user_query = models.User.objects.filter(username=usr)
user_obj = user_query.first()

# 签发:得到登录用户,签发token并存储在实例化对象中
if user_obj and user_obj.check_password(pwd): # auth组件查看方法
# 签发token,将token存放到 实例化类对象的token 名字中
payload = jwt_payload_handler(user_obj)
token = jwt_encode_handler(payload)
# 将当前用户与签发的token都保存在序列化对象中
self.user = user_obj
self.token = token
return attrs

raise serializers.ValidationError({'data': '数据有误'})
views.py
#实现多方式登陆签发token:账号、手机号、邮箱等登陆
# 1) 禁用认证与权限组件
# 2) 拿到前台登录信息,交给序列化类
# 3) 序列化类校验得到登录用户与token存放在序列化对象中
# 4) 取出登录用户与token返回给前台
import re
from . import serializers, models
from utils.response import APIResponse

from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler

class LoginAPIView(APIView):
# 1) 禁用认证与权限组件
authentication_classes = []
permission_classes = [] # 封装思想,将校验规则封装到序列化类中全局钩子,用到去调用就好了,复用性高
def post(self, request, *args, **kwargs):
# 2) 拿到前台登录信息,交给序列化类,规则:账号用usr传,密码用pwd传
user_ser = serializers.UserModelSerializer(data=request.data)
# 3) 序列化类校验得到登录用户与token存放在序列化对象中
user_ser.is_valid(raise_exception=True)
# 4) 取出登录用户与token返回给前台
return APIResponse(token=user_ser.token, results=serializers.UserModelSerializer(user_ser.user).data)

# "一根筋" 思考方式:所有逻辑都在视图类中处理,复用性低
def my_post(self, request, *args, **kwargs):
usr = request.data.get('usr')
pwd = request.data.get('pwd')
if re.match(r'.+@.+', usr):
user_query = models.User.objects.filter(email=usr)
elif re.match(r'1[3-9][0-9]{9}', usr):
user_query = models.User.objects.filter(mobile=usr)
else:
user_query = models.User.objects.filter(username=usr)
user_obj = user_query.first()
if user_obj and user_obj.check_password(pwd):
payload = jwt_payload_handler(user_obj)
token = jwt_encode_handler(payload)
return APIResponse(results={'username': user_obj.username}, token=token)
return APIResponse(data_msg='不可控错误')
from django.conf.urls import url
from . import views
from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token
urlpatterns = [
# url(r'^jogin/$', ObtainJSONWebToken.as_view()),
url(r'^jogin/$', obtain_jwt_token),
url(r'^user/detail/$', views.UserDetail.as_view()),
url(r'^login/$', views.LoginAPIView.as_view()),
]

路由

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

2. 案例:自定义认证反爬规则的认证类  验证token

除了以下的方式,我们还可以自定义很多规则来反爬,比如后期我们设计网站后台规定前台传来的数据:authtoken  token  jwt ,

这样就可以实现动态加密,用户不同加密秘钥不同,后台用auth和token拼接加密后去碰撞检验

authentications.py
import jwt
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework.exceptions import AuthenticationFailed

class JWTAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_token = request.META.get('HTTP_AUTHORIZATION')

# 自定义校验规则:下面的一个方法
token = self.parse_jwt_token(jwt_token)

if token is None:
return None

try:
# token => payload
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('token已过期')
except: # 简化异常处理过程,其他全部当成是非法用户,异常捕获支持多个except,
raise AuthenticationFailed('非法用户')
# payload => user
user = self.authenticate_credentials(payload)
return (user, token)

# 自定义校验规则:auth token jwt,auth为前盐,jwt为后盐
def parse_jwt_token(self, jwt_token):
tokens = jwt_token.split() # 按照空格切分
if len(tokens) != 3 or tokens[0].lower() != 'auth' or tokens[2].lower() != 'jwt':
return None
return tokens[1] # 返回掐头去尾后的真正的token
views.py
from rest_framework.views import APIView
from utils.response import APIResponse
# 必须登录后才能访问 - 通过了认证权限组件
from rest_framework.permissions import IsAuthenticated
# 自定义jwt校验规则
from .authentications import JWTAuthentication # 自定义的认证类

class UserDetail(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
return APIResponse(results={'username': request.user.username})
urlpatterns = [
# url(r'^jogin/$', ObtainJSONWebToken.as_view()),
url(r'^jogin/$', obtain_jwt_token), # 自动签发固定写法
url(r'^user/detail/$', views.UserDetail.as_view()),
]

路由

六、admin使用自定义User表:新增用户密码密文

原本的admin后台新增用户界面,密码显示为明文显示

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

我们可以在后台修改实现密码密文显示:

api/admin.py

from django.contrib import admin
from . import models # 自定义User表,admin后台管理,采用密文密码
from django.contrib.auth.admin import UserAdmin
admin.site.register(models.User, UserAdmin)

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

只需要这两句就可以实现密码密文了,但是这样的话只有用户名和密码,其他信息都没了,
因为UserAdmin源码内部add_fieldsets内只有这两个字段,如果想像原来一样显示很多信息,可以重写add_fieldsets
下面示例增加电话和邮箱,其他字段可百度,有全套的
from django.contrib import admin
from . import models
# 自定义User表,admin后台管理,采用密文密码
from django.contrib.auth.admin import UserAdmin class MyUserAdmin(UserAdmin):
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2', 'mobile', 'email'),
}),
) admin.site.register(models.User, MyUserAdmin)

9) drf  JWT 认证   签发与校验token 多方式登陆  自定义认证规则反爬  admin密文显示

七、

上一篇:ESLint的使用笔记


下一篇:数据结构:链表(python版)续:带有尾节点引用的单链表