目录
一、认证模块工作原理
源码分析:
首先:在APIView的dispatch中调用了initial方法进行了三大认证
- 认证模块:校验用户是否登陆: 登陆用户、非法用户、游客
- 权限模块:校验用户是否拥有权限: 校验对象是 登陆用户和游客
- 频率模块:访问接口的次数在设定的时间范围内是否过快:
def initial(self, request, *args, **kwargs): # 认证模块:校验用户是否登陆: 登陆用户、非法用户、游客 self.perform_authentication(request) # 权限模块:校验用户是否拥有权限: 校验对象是 登陆用户和游客 self.check_permissions(request) # 频率模块:访问接口的次数在设定的时间范围内是否过快: # 配置访问频率,每次访问都要缓存记次,超次后需要等待的事件 self.check_throttles(request)
进入
self.perform_authentication(request)
会发现其中只有一句话def perform_authentication(self, request): request.user
那我们首先就要知道request是谁,这个request是通过实例化request类来进行二次封装的request对象。
这个request对象来.user实际上是调用了Request类中的user方法,通过@property
装饰器来装饰的数据方法属性@property def user(self): if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user
进入这个方法,会发现有一句话
self._authenticate()
. 就是在这个方法内找到了认证模块的配置类,并调用了该类的authenticate(self)
方法,并把self传了进去。该self就是request对象,得到返回值是一个用户和认证的元祖def _authenticate(self):w for authenticator in self.authenticators: # 重点,找到配置类,并循环 try: # 调用改类的authenticate方法,并传参,self就是request对象,得到返回值是一个用户和认证的元祖 user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple return self._not_authenticated()
进入这个方法, 会发现有一句话
for authenticator in self.authenticators:
,这里是重点- self是谁?是Request对象,它的authenticators就是在init初始化方法中赋值的
def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): self.authenticators = authenticators or ()
那么
self.authenticators
赋值的内容是什么呢?就是在二次封装Request实例化对象是传进去的参数def initialize_request(self, request, *args, **kwargs): # 准备要解析的内容字典 parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), # 解析模块,在封装request时,将数据一并解析了 authenticators=self.get_authenticators(), # 认证模块 negotiator=self.get_content_negotiator(), parser_context=parser_context )
此时才知道
self.get_authenticators()
才是真正获取认证模块配置信息的内容,里面和之前的模块一样,导入配置def get_authenticators(self): return [auth() for auth in self.authentication_classes]
self.authentication_classes
的配置信息就是全局配置的内容。因此我们也可以通过重写该属性来完成局部配置。该全局配置的默认配置内容就是:authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES '''settings.py APISettings类''' 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ],
发现默认的配置就是使用
authentication
文件下的SessionAuthentication
类和BasicAuthentication
二、认证模块使用方法
注:认证模块默认只要有一个认证通过得到user用户就可以
通过上面的源码分析。我们知道全局配置和局部配置的实现方法。
全局配置:
# drf的配置 REST_FRAMEWORK = { # 认证组件的全局配置 'DEFAULT_AUTHENTICATION_CLASSES':[ # 'rest_framework.authentication.SessionAuthentication', # 'rest_framework.authentication.BasicAuthentication', # 自定义认证类 "api.utils.authentications.TokenAuthentication", ], }
局部配置:
from api.utils import authentications class UserListAPIView(ListAPIView): # 自定义局部配置 authentication_classes = [authentications.TokenAuthentication]
2.1 自定义认证类的实现方法
自定义认证规则:
请求格式: key: AUTHORIZATION value: token eyJ1c2VybmFtZSI6ICJjaGVuIn0=.eyJwayI6IDJ9.4a1884a2ac002993bbb93b68bc45fa1a
- 继承BaseAuthentication类,重写authenticate方法
- 认证规则(authenticate方法实现体):
- 没有携带认证信息,直接返回None => 游客
- 有认证信息,校验失败,抛异常 => 非法用户
- 有认证信息,校验出User对象 => 合法用户
# 自定义认证类 from rest_framework.authentication import BaseAuthentication # 认证异常 from rest_framework.exceptions import AuthenticationFailed """ 认证模块工作原理 1)继承BaseAuthentication类,重写authenticate方法 2)认证规则(authenticate方法实现体): 没有携带认证信息,直接返回None => 游客 有认证信息,校验失败,抛异常 => 非法用户 有认证信息,校验出User对象 => 合法用户 """ from . import common class TokenAuthentication(BaseAuthentication): prefix = 'Token' def authenticate(self, request): # 通过前台的请求头来进行token认证信息校验 auth = request.META.get('HTTP_AUTHORIZATION') # 没有直接返回None => 游客 if not auth: return None auth_list = auth.split() # 有认证信息,校验失败,抛异常 => 非法用户 if len(auth_list) != 2 or auth_list[0].lower() != self.prefix.lower(): raise AuthenticationFailed("非法用户") # 取出token token = auth_list[1] # 校验token,失败抛异常,成功返回(user, token) user_obj = common._get_obj(token) return (user_obj, token)
# 校验算法(认证类) """ 拆封token:一段 二段 三段 用户名:b64decode(一段) 用户主键:b64decode(二段) 碰撞解密:md5(用户名+用户主键+服务器秘钥) == 三段 """ # 认证异常 from rest_framework.exceptions import AuthenticationFailed from ..models import UserInfo def _get_obj(token): token_list = token.split(".") # token长度不符合 if len(token_list) != 3: raise AuthenticationFailed("非法用户") username = json.loads(base64.b64decode(token_list[0])).get('username') pk = json.loads(base64.b64decode(token_list[1])).get('pk') md5_dic = { 'username': username, 'pk': pk, 'key': settings.SECRET_KEY } print(md5_dic) if token_list[2] != hashlib.md5(json.dumps(md5_dic).encode()).hexdigest(): raise AuthenticationFailed('token内容异常') user_obj = UserInfo.objects.get(pk=pk, username=username) return user_obj
'''views.py''' # 查看所有用户信息,前提:必须是登录的超级管理员 from api.utils import authentications from api.utils import permissions from rest_framework.generics import ListAPIView class UserInfoListAPIView(ListAPIView): # 同电商网站,多接口是不需要登录的,少接口需要登录,使用在需要登录的接口中完成局部配置,进行局部接口校验 authentication_classes = [authentications.TokenAuthentication] permission_classes = [permissions.SuperUserPermission] # 获取除了管理员的所有用户 queryset = models.UserInfo.objects.filter(is_active=True, is_superuser=False) serializer_class = serializers.UserInfoModelSerializer
2.2 通过序列化模块实现登陆,通过认证模块进行校验
# 登录接口:如果是超级管理员登录,返回一个可以校验出超级管理员的token字符串 # 只要有用户登录,就可以返回一个与登录用户相关的token字符串 => 返回给前台 => 签发token => user_obj -> token_str class LoginAPIView(APIView): # 登录接口一定要做:局部禁用 认证 与 权限 校验 authentication_classes = [] permission_classes = [] def post(self,request,*args,**kwargs): ser_obj = serializers.LoginModelSerializer(data=request.data) ser_obj.is_valid(raise_exception=True) return common.APIResponse(data={ 'username': ser_obj.user.username, 'token': ser_obj.token })
# 用户登陆的接口 from django.contrib import auth from . import common class LoginModelSerializer(serializers.ModelSerializer): # username和password字段默认会走系统校验,而系统的post请求校验,一定当做增方式校验,所以用户名会出现 重复 的异常 # 所以自定义两个反序列化字段接收前台的账号密码 user = serializers.CharField(write_only=True) pwd = serializers.CharField(write_only=True) class Meta: model = models.UserInfo fields = ["user","pwd"] def validate(self, attrs): user = attrs.get('user') pwd = attrs.get('pwd') try: user_obj = auth.authenticate(username=user, password=pwd) except: raise serializers.ValidationError({'user': '账号或密码错误'}) if not user_obj: raise serializers.ValidationError({'user': '账号或密码错误'}) # 拓展名称空间 self.user = user_obj # 放token self.token = common._get_token(user_obj) return attrs
# 签发算法(认证类) # 自定义签发token # 分析:拿user得到token,后期还需要通过token得到user # token:用户名(base64加密).用户主键(base64加密).用户名+用户主键+服务器秘钥(md5加密) # eg: YWJj.Ao12bd.2c953ca5144a6c0a187a264ef08e1af1 # 签发算法:b64encode(用户名).b64encode(用户主键).md5(用户名+用户主键+服务器秘钥) """ 封装token:一段 二段 三段 用户名:b64decode(一段) 用户主键:b64decode(二段) 三段加密:md5(用户名+用户主键+服务器秘钥) == 三段 """ from django.conf import settings def _get_token(obj): username = base64.b64encode( json.dumps({"username": obj.username}).encode("utf8") ).decode("utf8") pk = base64.b64encode( json.dumps({"pk": obj.id}).encode("utf8") ).decode("utf8") t3_json = json.dumps({ 'username': obj.username, 'pk': obj.id, 'key': settings.SECRET_KEY }) t3 = hashlib.md5(t3_json.encode()).hexdigest() return '%s.%s.%s' % (username, pk, t3)
三、权限模块使用方法
全局配置:
# drf的配置 REST_FRAMEWORK = { # 权限模块的全局配置 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', 'rest_framework.permissions.IsAuthenticated', # 自定义权限类 "api.utils.permissions.SuperUserPermission", ], }
局部配置:
from api.utils import authentications class UserListAPIView(ListAPIView): # 自定义局部配置 permission_classes = [permissions.SuperUserPermission]
3.1 系统的权限类
注:配置的权限类,必须全部通过权限校验才算通过
系统的权限类: AllowAny:不限制 IsAuthenticated:必须是登录用户 IsAdminUser:必须是后台用户 IsAuthenticatedOrReadOnly:读操作无限制,其他操作需要登录
3.2 自定义权限类的实现方法
权限校验可以实现多个,必须全部通过
- 继承BasePermission类,重写has_permission方法
- 权限规则(has_permission方法实现体):
- 返回True,代表有权限
- 返回False,代表无权限
# 自定义权限类 from rest_framework.permissions import BasePermission """ 权限模块工作原理 1)继承BasePermission类,重写has_permission方法 2)权限规则(has_permission方法实现体): 返回True,代表有权限 返回False,代表无权限 """ class SuperUserPermission(BasePermission): def has_permission(self, request, view): print(request.user) print(request.auth) return request.user and request.user.is_superuser
四、频率模块使用方法
频率模块就是显示规定时间内的访问次数
drf默认的频率配置是空,也就是说,如果需要我们就要自己去实现
- 定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
- scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置
- get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key
自定义实现频率模块
'''throttles.py''' # 自定义频率类 from rest_framework.throttling import SimpleRateThrottle # 1)定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性 # 2)scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置 # 3)get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key class ThreeTimeUserThrottle(SimpleRateThrottle): # 对应的频率设置,会去settings.py中找相应的配置 scope = 'three' ''' 设置属性rate也可以直接进行频率配置 如:rate = "3/min" ''' # 当前用户缓存的key def get_cache_key(self, request, view): return 'throttle:user_%s' % (request.user.id)
全局配置:
# drf的配置 REST_FRAMEWORK = { # 频率模块的全局配置 'DEFAULT_THROTTLE_RATES': { # 自定义频率设置 'three': '3/min', }, }
局部配置:
from rest_framework_jwt import authentication class UserInfoListAPIView(ListAPIView): '''频率模块局部配置''' throttle_classes = [throttles.ThreeTimeUserThrottle]