zoukankan      html  css  js  c++  java
  • drf中认证源码流程

    drf中认证流程

    首先通过导入from rest_framework.views import APIView,然后通过ctrl+鼠标右键进入到APIView类中,apiview中定义了许多方法,我们首先找到dispatch方法,因为定义路由时候,通过指定url找到对应的视图类,首先会执行dispatch方法

    ## dispatch源码
    def dispatch(self, request, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs
            # initialize_request() 返回初始请求对象
            request = self.initialize_request(request, *args, **kwargs)
            self.request = request
            self.headers = self.default_response_headers  # deprecate?
    
            try:
                # initial()处理请求方法之前要做的一些检查,其中就包括认证检查,权限检查,节流等
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                ...   # 此处代码省略,这部分代码主要就是用来获取适当的请求方法
    

    然后我们点击self.initial(request, *args, **kwargs)

    # 主要就是处理请求方法前的一些校验
    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)  # 节流
    

    然后我们点击self.perform_authentication(request)

    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    # 这边调用的是drf(APIView)的Request的user属性
    

    然后我们点击user(在rest_framework下的request.py文件)

    #返回与当前请求关联的用户,作为已验证的用户,通过提供给请求的身份验证类。
    @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
    

    然后我们点击self._authenticate()

    #开始用户认证,如果验证成功后返回元组: (用户,用户Token)
    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        # [BasicAuthentication()对象,]
        # 循环得到每个对象
        for authenticator in self.authenticators:   # 这里的self指的是drf的request对象,看下面的分析
            try:
                # 调用对象的authenticate方法
                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  #返回一个元祖,user、auth
                return
    
         self._not_authenticated()
    

    再看下self._not_authenticated()

    #如果认证成功,执行_authenticate方法,若认证失败抛出异常调用self._not_authenticated()
         def _not_authenticated(self):
            """
            Set authenticator, user & authtoken representing an unauthenticated request.
    
            Defaults are None, AnonymousUser & None.
            """
            #如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
            self._authenticator = None
    
            if api_settings.UNAUTHENTICATED_USER:
                self.user = api_settings.UNAUTHENTICATED_USER()
            else:
                self.user = None
    
            if api_settings.UNAUTHENTICATED_TOKEN:
                self.auth = api_settings.UNAUTHENTICATED_TOKEN()
            else:
                self.auth = None
    

    我们可以从APIView类中找到initialize_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(),
                # [BasicAuthentication(),]  #实例化对象
                authenticators=self.get_authenticators(),
                negotiator=self.get_content_negotiator(),
                parser_context=parser_context
            )
    

    我们点击get_authenticators()

        def get_authenticators(self):
            """
            Instantiates and returns the list of authenticators that this view can use.
            """
            return [auth() for auth in self.authentication_classes] #实例化对象
    

    我们点击self.authentication_classes

    # api_settings文件中就是drf默认的验证器
    class APIView(View):
        ...
    	authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        ...
    

    我们点击api_settings.DEFAULT_AUTHENTICATION_CLASSES

    'DEFAULT_AUTHENTICATION_CLASSES': [
            'rest_framework.authentication.SessionAuthentication',  # session认证
            'rest_framework.authentication.BasicAuthentication'  # 基本认证
        ],
    

    然后我们来看一下上面的两个认证器类(rest_framework/authentication.py)

    # 导入
    from rest_framework.authentication import BaseAuthentication
    # 点开BaseAuthentication
    # 可以看到这是一个父类,所有认证器类都需要继承它,并且需要重写里面的两个方法authenticate和authenticate_header
    class BaseAuthentication:
        """
        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
    

    SessionAuthentication认证器类代码

    class SessionAuthentication(BaseAuthentication):
        """
        Use Django's session framework for authentication.
        """
    
        def authenticate(self, request):
            """
            Returns a `User` if the request session currently has a logged in user.
            Otherwise returns `None`.
            """
    
            # Get the session-based user from the underlying HttpRequest object
            user = getattr(request._request, 'user', None)
    
            # Unauthenticated, CSRF validation not required
            if not user or not user.is_active:
                return None
    
            self.enforce_csrf(request)
    
            # CSRF passed with authenticated user
            return (user, None)
    
        def enforce_csrf(self, request):
            """
            Enforce CSRF validation for session based authentication.
            """
            def dummy_get_response(request):  # pragma: no cover
                return None
    
            check = CSRFCheck(dummy_get_response)
            # populates request.META['CSRF_COOKIE'], which is used in process_view()
            check.process_request(request)
            reason = check.process_view(request, None, (), {})
            if reason:
                # CSRF failed, bail with explicit error message
                raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
    

    所以我们要自定义认证类,只需要参考上面给出的代码,重写父类的两个方法即可,当然rest_framework/authentication.py这个文件里面还有BasicAuthentication,TokenAuthentication,RemoteUserAuthentication这个几个类的代码,也可以参考

    自定义认证类

    import logging
    
    from django.conf import settings
    from rest_framework.authentication import (
        BaseAuthentication, get_authorization_header,
    )
    from rest_framework.exceptions import AuthenticationFailed, PermissionDenied
    from six import raise_from
    
    from .managers.pam import auth
    from .models import User
    
    logger = logging.getLogger(__name__)
    
    class JWTAuthentication(BaseAuthentication):
        # 默认会返回Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b,指定keyword则会返回使用指定的关键字Authorization: Jwt 9944b09199c62bcf941
        keyword = 'Jwt'
        # 自定义认证需要重写 authenticate 方法!
        def authenticate(self, request):
            # 由于 header 的头部是 byte 类型此方法 转换成 字符串 然后以空格为界 转换列表
            # AUTHORIZATION這个就是之前服务端返回的,只不过Django会默认加上HTTP
            auth = get_authorization_header(request).split()
            # auth = ["Token", "c4840b5226a65806c586c239345fce66caf12409"]
            if not auth or auth[0].lower() != self.keyword.lower().encode():
                return None
    
            if len(auth) == 1:
                raise AuthenticationFailed(
                    detail='Invalid token header. No credentials provided.'
                )
            elif len(auth) > 2:
                raise AuthenticationFailed(
                    detail='Invalid token header.'
                    'Token string should not contain spaces.'
                )
    
            try:
                token = auth[1].decode()# token = c4840b5226a65806c586c239345fce66caf12409
            except UnicodeError:
                raise AuthenticationFailed(
                    detail='Invalid token header.'
                    'Token string should not contain invalid characters.'
                )
            return self.authenticate_credentials(token)
        
        # token 认证!
        def authenticate_credentials(self, token):
            import jwt
            from jwt import InvalidTokenError
            try:
                payload = jwt.decode(
                    token, settings.SECRET_KEY,
                    options={
                        'verify_signature': True,
                        'verify_exp': True,
                        'verify_nbf': True,
                        'verify_iat': True,
                        'require_exp': True,
                        'require_nbf': True,
                        'require_iat': True,
                        'require_iss': True,
                        'require_jti': True,
                        'require_role': True,
                        'require_sub': True,
                        'require_mgt': True
                    }
                )
                # sub: jwt所面向的用户
                user = User.objects.get(username=payload['sub'])
    
                payload_role = User.get_role_value(payload['role'])
                if payload_role > user.role:
                    raise PermissionDenied(
                        'Insufficient permission.'
                    )
    
                return user, payload
            except InvalidTokenError as e:
                raise_from(
                    AuthenticationFailed, e
                )
            except User.DoesNotExist as e:
                raise_from(
                    AuthenticationFailed, e
                )
        #  “jwt” 自定义的字符串 生成的token前面
        def authenticate_header(self, request):
            return self.keyword
    
    
    class CookieAuthentication(JWTAuthentication):
        def authenticate(self, request):
            token = request.COOKIES.get('token')
            if token is None:
                return None
            token = token.decode()
    
            return self.authenticate_credentials(token)
    

    视图类中使用

    class view(APIView):
        authentication_classes = (
                JWTAuthentication,
                CookieAuthentication
            )
    

    settings.py配置文件中需要指定一下使用我们自定义的类路径

    REST_FRAMEWORK = {
    	# 认证器类
        'DEFAULT_AUTHENTICATION_CLASSES': (
            #'rest_framework.authentication.BasicAuthentication',  # 基本认证
            #'rest_framework.authentication.SessionAuthentication',  # session认证
            # 使用我们自定义的认证类
            'antilles.user.plugins.JWTAuthentication'
        ),
    }
    

    参考链接

    https://www.cnblogs.com/shi-qi/articles/9629399.html
    https://www.cnblogs.com/jiakecong/p/14880244.html
    https://www.cnblogs.com/u-damowang1/p/13554143.html
    

    -------------------------------------------

    个性签名:代码过万,键盘敲烂!!!

    如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!

  • 相关阅读:
    【咖啡の设备】便携式冰滴壶——Dripo 使用体验 Experience Report of Ice Drip Coffee Maker——Dripo
    【异常记录(八)】 This operation requires IIS integrated pipeline mode.
    【异常记录(七)】MVC:从客户端中检测到有潜在危险的 Request.Form 值 的解决方法 [转] A potentially dangerous Request.Form value was detected from the client
    SQL优化:清理生产环境中已失效字段基本步骤 SQL optimization: basic steps to clean up invalid fields in production environments
    [转] sql server 跨数据库调用存储过程 SQL server calls stored procedures cross-databases
    获取lambda表达式类型,获取attributes是注意事项
    [笔记] SQL性能优化
    MSSQL 重建索引(在线重建、控制最大处理器数 、MAXDOP )
    [笔记] SQL性能优化
    [笔记] SQL性能优化
  • 原文地址:https://www.cnblogs.com/weiweivip666/p/15798418.html
Copyright © 2011-2022 走看看