欢迎来到Cecilia陈的博客

孤独,是人一生最好的修行。

12 认证组件--authendication

一、什么是三大认证组件

在请求的时候,认证模块是在什么时候触发的,在我们请求url时,匹配路由之前的视图类继承的是APIView,在APIView类中的as_view()方法调用了dispath()方法中,通过当前路由匹配的视图对象调用了initial方法。

然后进行了三大认证

def initial(self, request, *args, **kwargs):
    # 认证模块:校验用户是否登陆: 登陆用户、非法用户、游客
    self.perform_authentication(request)
    # 权限模块:校验用户是否拥有权限: 校验对象是 登陆用户和游客
    self.check_permissions(request)
    # 频率模块:访问接口的次数在设定的时间范围内是否过快:
    #     配置访问频率,每次访问都要缓存记次,超次后需要等待的事件
    self.check_throttles(request)

二、认证组件(authenticate)源码分析

self.perform_authentication(request)

看到上面的认证方法时,我们首先要request是谁?

我们先看一下dispacth调用时调用的代码把

request解释如下:

二次封装的request对象来.user实际上是调用了Request类中的user方法通过@property装饰器来装饰的数据方法属性

@property
def user(self):
    if not hasattr(self, '_user'):
         # self是二次封装的request
         # 调用这个_authenticate()方法
        with wrap_attributeerrors():
            self._authenticate()
        return self._user

进入这个方法,会发现有一句话self._authenticate(). 就是在这个方法内找到了认证模块的配置类,并调用了该类的authenticate(self)方法,并把self传了进去。该self就是request对象,得到返回值是一个用户和认证的元祖

def _authenticate(self):
        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:
                # 将authenticator赋值给self对象的名称空间中
                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

三、如何配置认证模块

通过上面的源码分析。我们知道全局配置和局部配置的实现方法。

全局配置:

# 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]

四、自定义认证类的实现方法

自定义认证规则:

请求格式:
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

五、通过序列化实现登录,通过认证模块进行校验

# 登录接口:如果是超级管理员登录,返回一个可以校验出超级管理员的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
posted @ 2019-12-03 21:30  Cecilia陈  阅读(203)  评论(0编辑  收藏  举报