rest-framework(七)
目录
三大认证组件
认证组件
self.perform_authentication(request)
'''
用户权限关系 RBAC(Role-BasedAccessControl)
表:User、Group、Permission、UG关系表、UP关系表、GP关系表
传统的RBAC有两种:权限三表 => 权限五表(没有UP关系表)
Django中Auth组件采用的是 权限六表(在传统RBAC基础上增加UP关系表)
==用户管理表,一定要在第一次数据库迁移时完成==
'''
#在admin中注册自定义的User表,并继承UserAdmin,使在后台注册的用户密码是密文
#通过重写UserAdmin,自定义某些属性
from django.contrib import admin
from . import models
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
class UserAdmin(AuthUserAdmin):
# 添加用户页面可控制字段
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile'),
}),
)
# 用户列表展示页面显示字段
list_display = ('username', 'email', 'mobile', 'is_staff')
# 注册自定义User表,用admin管理,配置UserAdmin,定制化管理页面
admin.site.register(models.User, UserAdmin)
RBAC三表
RBAC六表
jwt认证
'''
1. 登录时,账号密码换成token,=>签发token算法
2. 处理需要登录后的请求,拿到前台传来的登录证明信息token
解析出登录用户user=>校验token算法(代码)
'''
jwt认证集群图
2
jwt优点
"""
jwt: json web token
优点:
1)数据库不需要存储token,所以服务器的 IO 操作会减少(没有IO写操作)
2)客户端存Token,服务器只存储签发与校验算法,执行效率高
3)签发与校验算法在多个服务器上可以直接统一,所以jwt认证规则下,服务器做集群非常便捷
突破点:
1)token必须要有多个部分组成,有能反解的部分,也要有不能反解的部分 - jwt采用的都是三段式
2)token中必须包含过期时间,保证token的安全性与时效性
"""
jwt原理
'''
jwt原理:
1)jwt由 头.载荷.签名 三部分组成
2)每一部分数据都是一个json字典,头和载荷采用 base64 可逆加密算法加密,签名采用 HS256 不可逆加密
内容:
1)头(基本信息):可逆不可逆采用的加密算法、公司名称、项目组信息、开发者信息...
{
"company": "小女孩",
...
}
2)载荷(核心信息):用户主键、用户账号、客户端设备信息、过期时间...
{
'pk': 1,
...
}
3)签名(安全信息):头的加密结果、载荷的加密结果、服务器的安全码(盐)...
{
"header": "..."
...
}
'''
签发算法
'''
签发算法:
1)头内容写死(可以为空{}):公司、项目组信息都是固定不变的
=> 将数据字典转化成json字符串,再将json字符串加密成base64字符串
2)载荷的内容:用户账号、客户端设备信息是由客户端提供,用户主键是客户端提供账号密码校验User表通过后才能确定,过期时间根据当前时间与配置的过期时间相结合产生
=> 将数据字典转化成json字符串,再将json字符串加密成base64字符串
3)签名的内容,先将头的加密结果,载荷的加密结果作为成员,再从服务器上拿安全码(不能让任何客户端知道),也可以额外包含载荷的部分(用户信息,设备信息)
=> 将数据字典转化成json字符串,再将json字符串不可逆加密成HS256字符串
4)将三个字符串用 . 连接产生三段式token
校验算法:
1)从客户端提交的请求中拿到token,用 . 分割成三段(如果不是三段,非法)
2)头(第一段)可以不用解密
3)载荷(第二段)一定需要解密,先base64解密成json字符串,再转换成json字典数据
i)用户主键与用户账号查询User表确定用户是否存在
ii)设备信息用本次请求提交的设备信息比对,确定前后是否是同一设备,决定是否对用户做安全提示(eg:短信邮箱提示异地登录)(同样的安全保障还可以为IP、登录地点等)
iii)过期时间与当前时间比对,该token是否在有效时间内
4)签名(第三段)采用加密碰撞校验
i)将头、载荷加密字符串和数据库安全码形成json字典,转换成json字符串
ii)采用不可逆HS256加密形成加密字符串
iii)新的加密字符串与第三段签名碰撞比对,一致才能确保token是合法的
5)前方算法都通过后,载荷校验得到的User对象,就是该token代表的登录用户(Django项目一般都会把登录用户存放在request.user中)
'''
刷新算法
'''
刷新算法:
1)要在签发token的载荷中,额外添加两个时间信息:第一次签发token的时间,最多往后刷新的有效时间
2)每一请求携带token,不仅走校验算法验证token是否合法,还要额外请求刷新token的接口,完成token的刷新:校验规则与校验算法差不多,但是要将过期时间后移(没有超过有效时间,产生新token给客户端,如果超过了,刷新失败)
3)所以服务器不仅要配置过期时间,还需要配置最长刷新时间
'''
自定义jwt配置
'''
#1)在settings中配置
import datetime
JWT_AUTH = {
# 过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
# 是否允许刷新
'JWT_ALLOW_REFRESH': True,
# 最大刷新的过期时间
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
'''
登录接口,提供username 和password,签发token : ObtainJSONWebToken
校验接口,提供token,返回token就代表校验通过 : VerifyJSONWebToken
刷新接口,通过token,刷新新token返回 : RefreshJSONWebToken
认证类的使用
自定义认证类
'''
1. 如果使用session认证,drf默认提供了SessionAuthentication
2. 如果使用drf-jwt认证框架,drf-jwt框架提供了JSONWebTokenAuthentication
3. 如果是自定义签发与校验token,才需要将校验token的算法封装到自定义的认证类中
'''
#1)首先建立一个authentications的py文件,在该文件中自定义MyAuthentication认证组件类,该类继承BaseAuthentication
from rest_framework.authentication import BaseAuthentication
class MyAuthentication(BaseAuthentication):
"""
1) 从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)
-- 如果设置了反爬等措施,校验一下反爬(头 token)
2) 重写authenticate方法
3)没有token,返回None,代表游客
4)有token,进入校验
-- 不通过:抛AuthenticationFailed异常,代表非法用户
-- 通过:返回 (user, token),代表合法用户
"""
def authenticate(self, request):
print('来啦啦啦啦')
pass
#2)在BaseAuthentication类中规定了客户端携带token时的结构,已经校验token是否合法和正确
#-----------------------------------------------------------------------------------
class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
# 认证组件
# 配置自定义认证类(需求小)
authentication_classes = [authentications.MyAuthentication]
# 配置drf-jwt框架的认证类(需求大) => 配置还可以在全局(认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局)
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# authentication_classes = [JSONWebTokenAuthentication]
使用drf封装的认证组件
# 配置drf-jwt框架的认证类(需求大) => 配置还可以在全局(认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局)
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication]
#全局配置认证组件
# 在settings中配置
# drf框架自定义配置
REST_FRAMEWORK = {
# 认证组件
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.SessionAuthentication',
# 'rest_framework.authentication.BasicAuthentication'
'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
],
权限组件
self.check_permissions(request)
# 权限组件
# 配置自定义权限类(有需求)
permission_classes = [permissions.MyPermission]
# 配置drf自带的权限类(有需求)
from rest_framework.permissions import IsAuthenticated, IsAdminUser, AllowAny, IsAuthenticatedOrReadOnly
permission_classes = [IsAuthenticated]
permission_classes = [IsAdminUser]
原生drf权限组件
- AllowAny:游客和登录用户有全权限
- IsAuthenticated:只有登录用户有全权限
- IsAdminUser:只有后台用户(admin用户)有全权限
- IsAuthenticatedOrReadOnly:游客有读权限,登录用户有全权限
自定义权限组件
"""
如果有特殊需要,需要自定义权限类
如:只有superuser有权限、只有vip用户有权限、只有某ip网段用户有权限、只有某个视图及其子类有权限
"""
class MyPermission(BasePermission):
def has_permission(self, request, view):
"""
1) 根据需求,request和view的辅助,制定权限规则判断条件
2)如果条件通过,返回True
3)如果条件不通过,返回False
"""
print(request.user, request.auth)
return False
#--------------------------------------------------------------------------------
class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
# 权限组件
# 配置自定义权限类(有需求)
permission_classes = [permissions.MyPermission]
jwt签发token
jwt签发token源码分析
多方式登录签发token
多方式登录,一定要自定义jwt-token的签发 --> 自己定义login视图
- token只能由 登录接口 签发
- 登录接口也是APIView的子类,使用一定会进行 认证、权限 组件的校验
结论:
不管系统默认、或是全局settings配置的是何认证与权限组件,登录接口不用参与任何认证与权限的校验
所以,登录接口一定要进行 认证与权限 的局部禁用
# 多方式登录
from rest_framework.views import APIView
class LoginAPIView(APIView):
authentication_classes = []
pagination_class = []
def post(self, request, *args, **kwargs):
serializer = serializers.LoginModelSerializer(data=request.data)
serializer.is_valid(raise_exception=True) # 内部在全局钩子中完成token的签发
return APIResponse(results={
'username': serializer.content.get('user').username,
'token': serializer.content.get('token')
})
#---------在serializers中自定义登录时所要反序列化的字段,以及校验账号密码----
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
import re
class LoginModelSerializer(serializers.ModelSerializer):
# post请求,序列化默认当做create动作进行校验,需要校验数据库,create动作username会抛用户已存在异常
# 抛用户已存在异常是多余的,所以自定义系统校验规则即可
username = serializers.CharField(min_length=3, max_length=16)
password = serializers.CharField(min_length=3, max_length=16)
class Meta:
model = models.User
fields = ('username', 'password')
# 用全局钩子,完成token的签发
def validate(self, attrs):
# 1)通过 username 和 password 完成多方式登录校验,得到user对象
user = self._validate_user(attrs)
# 2)user对象包装payload载荷
payload = jwt_payload_handler(user)
# 3)payload载荷签发token
token = jwt_encode_handler(payload)
# 4)将user与token存储到serializer对象中,方便在视图类中使用
self.content = {
'user': user,
'token': token
}
return attrs
def _validate_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if re.match(r'.*@.*', username): # 邮箱
user = models.User.objects.filter(email=username).first() # type: models.User
elif re.match(r'^1[3-9][0-9]{9}$', username): # 电话
user = models.User.objects.filter(mobile=username).first()
else: # 用户名
user = models.User.objects.filter(username=username).first()
if not user or not user.check_password(password):
raise serializers.ValidationError({'message': '用户信息异常'})
return user
认证组件与权限组件绑定使用
- 每一个视图类都要进行认证校验,且认证规则一致,所以全局配置认证类即可
- 每一个视图类都要进行权限校验,默认配置的是不限制(AllowAny),但实际开发中,视图类的访问权限不尽相同,所以要在具体 的视图类,配置具体的权限规则
from rest_framework.viewsets import ViewSet
class UserViewSet(ViewSet):
# 权限:只有VIP用户可以查看个人详细详细
permission_classes = [permissions.VIPUserPermission]
def retrieve(self, request, *args, **kwargs):
return APIResponse(results={
'username': request.user.username,
'email': request.user.email,
'mobile': request.user.mobile,
'data_joined': request.user.date_joined,
})
#----------自定义权限组件-------------------------------
# VIP用户权限
class VIPUserPermission(BasePermission):
def has_permission(self, request, view):
for group in request.user.groups.all():
if group.name.lower() == 'vip':
return True
return False
频率组件
self.check_throttles(request)
原生drf封装的频率组件
- AnonRateThrottle : 登录客户无限制访问
- UserRateThrottle : 对所有用户设置频率限制
自定义频率类
from rest_framework.throttling import SimpleRateThrottle
"""
如果有特殊需要,需要自定义频率类
如:对ip进行限次、对电话进行限制、对视图某些信息进行限次
"""
class MobileRateThrottle(SimpleRateThrottle):
"""
1)设置scope字符串类属性,同时在settings中进行drf配置DEFAULT_THROTTLE_RATES
eg: DEFAULT_THROTTLE_RATES = {'mobile': '1/min'}
2)重写get_catch_key方法:
返回与限制条件有关的字符串,表示限制
返回None,表示不限制
"""
scope = 'mobile'
def get_cache_key(self, request, view):
if not request.user.is_authenticated or not request.user.mobile:
return None # 匿名用户 或 没有电话号的用户 都不限制
# 只要有电话号的用户踩进行限制
return self.cache_format % {
'scope': self.scope,
'ident': request.user.mobile
}
#------------views视图类中--------------------------------------------------
class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
# 频率组件
# 配置drf自带的频率类(有需求)
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
# throttle_classes = [UserRateThrottle]
# 配置自定义的频率类(需求大)
throttle_classes = [throttles.MobileRateThrottle]