• rest framework之认证组件


    一、认证源码流程

    1、认证准备工作

    rest framework之APIView中提到过rest framework的视图不仅有CBV分发的特性,而且又对request进行了封装,其中封装的就有认证功能。在APIView类下的dispatch方法中:

     def dispatch(self, request, *args, **kwargs):
            """
            `.dispatch()` is pretty much the same as Django's regular dispatch,
            but with extra hooks for startup, finalize, and exception handling.
            """
            self.args = args
            self.kwargs = kwargs
            #rest-framework重构request对象
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            self.headers = self.default_response_headers  # deprecate?
            try:
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                #这里和CBV一样进行方法的分发
                if request.method.lower() in self.http_method_names:
                    handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
                else:
                    handler = self.http_method_not_allowed
    
                response = handler(request, *args, **kwargs)
    
            except Exception as exc:
                response = self.handle_exception(exc)
    
            self.response = self.finalize_response(request, response, *args, **kwargs)
            return self.response

    在dispatch方法中:

      request = self.initialize_request(request, *args, **kwargs)

    是重构request对象,initialize_request返回的就是一个request对象,封装了原request以及认证相关:

        def initialize_request(self, request, *args, **kwargs):
            """
            Returns the initial request object.
            """
            parser_context = self.get_parser_context(request)
    
            return Request(
                request, #传入原request
                parsers=self.get_parsers(),
                authenticators=self.get_authenticators(), #认证相关
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )

    在request对象中封装了authenticators这一个个写好的认证类的对象,可以自己配置的认证类,这样认证的准备工作已经完成了(其实就是request封装认证类对象的列表)。

    2、认证过程

    在dispatch方法中的代码中:

     self.initial(request, *args, **kwargs) #request是已经重构的request

    在initial方法中:

        def initial(self, request, *args, **kwargs):
        
            ...
    
            # Ensure that the incoming request is permitted
            self.perform_authentication(request) #进行认证
    
            self.check_permissions(request)
            self.check_throttles(request)    

    在perform_authentication方法中:

        def perform_authentication(self, request):
            """
            Perform authentication on the incoming request.
    
            Note that if you override this and simply 'pass', then authentication
            will instead be performed lazily, the first time either
            `request.user` or `request.auth` is accessed.
            """
            request.user

    执行的是request.user,此时引出了之前准备工作的request封装对象

    #方便寻找,导入
    from rest_framework.request import Request
        @property
        def user(self):
            """
            Returns the user associated with the current request, as authenticated
            by the authentication classes provided to the request.
            """
            if not hasattr(self, '_user'):
                with wrap_attributeerrors():
                    self._authenticate()
            return self._user

    在_authenticate方法中

        def _authenticate(self):
            """
            Attempt to authenticate the request using each authentication instance
            in turn.
            """
            """
            三种情况:
            1、正常的认证,元组不为空
            2、认证失败,没有通过认证类中认证方法的检验,没有登陆
            3、匿名用户,认证类中认证方法没有检验,返回的是None
            """
            for authenticator in self.authenticators: #循环取出每一个认证类的对象
                try:
                    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() #匿名用户执行
     def _not_authenticated(self):
            """
            Set authenticator, user & authtoken representing an unauthenticated request.
    
            Defaults are None, AnonymousUser & None.
            """
            self._authenticator = None
    
            if api_settings.UNAUTHENTICATED_USER:
                self.user = api_settings.UNAUTHENTICATED_USER() #在settings中可以设置默认的
            else:
                self.user = None #如果settings中没设置默认的就执行
    
            if api_settings.UNAUTHENTICATED_TOKEN:
                self.auth = api_settings.UNAUTHENTICATED_TOKEN()
            else:
                self.auth = None
    _not_authenticated

    注意,认证类最好继承rest framework默认的基类,当然没有也是可行的

    #基类
    class BaseAuthentication(object):
        """
        All authentication classes should extend BaseAuthentication.
        """
    
        #继承的必须重写,否则抛异常
        def authenticate(self, request):
            """
            Authenticate the request and return a two-tuple of (user, token).
            """
            raise NotImplementedError(".authenticate() must be overridden.")
    
        #继承必须有这个方法
        def authenticate_header(self, request):
            """
            Return a string to be used as the value of the `WWW-Authenticate`
            header in a `401 Unauthenticated` response, or `None` if the
            authentication scheme should return `403 Permission Denied` responses.
            """
            pass

    其它模块导入的方式:

    from rest_framework.authentication import BasicAuthentication

    3、认证配置

    那么一个视图如何使用认证类呢?

    首先,在dispatch方法中知道request对认证进行了前期的准备工作,封装了一个个的认证类对象列表。这些认证类是从哪里读取的呢?

        def get_authenticators(self):
            """
            Instantiates and returns the list of authenticators that this view can use.
            """
            return [auth() for auth in self.authentication_classes] #获取认证类对象列表
    • 局部认证

    如果在视图中设置了authentication_classes=[],就会优先使用我们自己配置的:

    class IndexView(APIView):
        
        authentication_classes = [AuthToken] #认证类
        
        def get(self,request):
            pass
    
        def post(self,request):
             pass
    • 全局认证

    如果没有进行视图的设置呢?此时就会去配置文件中寻找:

     authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    
    
    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
    
    def reload_api_settings(*args, **kwargs):
        setting = kwargs['setting']
        if setting == 'REST_FRAMEWORK': #配置文件中的键值
            api_settings.reload()

    在settings文件中设置:

    REST_FRAMEWORK = {
        # 全局使用认证类
        "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.AuthToken', ],#注意里面是路径,将认证类写在utils文件夹的auth.py中
    }

    这样,既可以做全局认证,又可以做局部认证,当全局认证中某一个视图不做认证的话(比如登陆),只需要在其视图中将authentication_classes 列表置空即可。

    • 匿名用户配置

    在认证类中的认证方法authenticate中执行的有三种结果,如果返回的结果是None,就会返回AnonymousUser,在settings.py中也是可以设置它的返回值的

    REST_FRAMEWORK = {
    
        # "UNAUTHENTICATED_USER":lambda :"匿名用户"
        "UNAUTHENTICATED_USER":None, # 匿名,request.user = None
        "UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None
    }

    总结:

    二、实现

     1、认证类

    from rest_framework.authentication import BaseAuthentication
    import time
    import hashlib
    from crm.models import *
    from rest_framework.exceptions import AuthenticationFailed
    
    class AuthToken(BaseAuthentication):
        """
        认证类
        """
        def authenticate(self, request):
            """
            Authenticate the request and return a two-tuple of (user, token).
            在请求头中获取token进行验证,如果前端的请求头是Authorization,django请求头必须以大写HTTP开头,
            中间以_分隔的大写键值
            """
            token = request._request.META.get("HTTP_AUTHORIZATION")
            token_obj = UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise AuthenticationFailed("用户未登陆!")
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            """
            Return a string to be used as the value of the `WWW-Authenticate`
            header in a `401 Unauthenticated` response, or `None` if the
            authentication scheme should return `403 Permission Denied` responses.
            """
            pass
    
    
    def get_md5(username):
        """
        生成对应用户的token
        :param username: 用户名
        :return:
        """
        m=hashlib.md5(bytes(username,encoding="utf-8")) #加盐
        ctime = str(time.time())
        m.update(bytes(ctime,encoding="utf-8"))
        return m.hexdigest()

    2、使用

    class UserModelView(viewsets.ModelViewSet):
        authentication_classes = [AuthToken,] #配置认证类
        #认证成功后可以拿到元组参数 request.user,request.auth
        queryset = models.UserInfo.objects.all()
        serializer_class = AuthorModelSerlizer = UserModelSerlizer

    如果认证失败的话就会返回认证类中抛出的异常:

    {
    
      ”ddetail“:"用户未登陆"
    
    }

    配置认证类也可以在settings.py中进行全局配置:

    REST_FRAMEWORK = {
        # 全局使用认证类
        "DEFAULT_AUTHENTICATION_CLASSES":['crm.utils.auth.AuthToken', ],
    
    }

    3、前端提交token

    除了登陆页面外的其它页面都应该提供token,这里在提交数据时将token附带在请求头中提交

      getUsers() {
            //将token设置在请求头中提交
            this.$http.defaults.headers.common['Authorization'] = localStorage.getItem("token");
            this.$store.dispatch('user/getAllUserList',this)//this是Vue实例
    
          }
  • 相关阅读:
    Python批量 png转ico
    线性回归
    【论文集合】多语言机器翻译
    【论文集合】curriculum learning总结/课程学习论文
    crf++分词
    强化学习的探索策略方式
    关于multiprocessing中的logging的数据打印到本地失败或重复问题
    置信区间绘图、以10次平均值为例
    打印CSDN博客内容JS
    SUMO学习笔记(3)
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11387324.html
Copyright © 2020-2023  润新知