restframework(5):认证组件

restframework(5):认证组件

  • 认证功能简介
    • 认证功能说明
    • restframework中的认证
  • 认证快速实例
    • 快速案例:自定义认证类
    • 局部使用
    • 全局使用
    • 使用认证类生成token
    • 使用token进行验证
  • 认证源码解析
    • View -> dispatch方法
    • initial初始化
    • request的封装与初始化
    • _authenticate()和_not_authenticated函数
  • 总结

认证功能简介

Django 自带一个用户认证系统,这个系统处理用户帐户、组、权限和基于 cookie 的会话。本文将一步步的解读restframework中的认证类。

认证功能说明

  1. 用户登陆后才能访问某些页面。

  2. 如果用户没有登录就访问该页面的话直接跳到登录页面。

  3. 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址。

restframework中的认证

在restframework中,认证即是通过继承BaseAuthentication重构认证的类,认证的逻辑在类的authenticate方法中实现,通过判断用户传递的信息,如果认证成功返回(用户,用户Token)元组,会将用户对象封装到request里,通过request.用户可以获得用户的相关信息。


认证快速实例

快速案例:自定义认证类

我们针对第三篇博文的出版社、图书、作者另外加上下面两个类:
models.py:

class User(models.Model):
    name=models.CharField(max_length=32)
    pwd=models.CharField(max_length=32)

class Token(models.Model):
    user=models.OneToOneField("User")
    token = models.CharField(max_length=128)

    def __str__(self):
        return self.token

首先我们在models中创建两张表,其中一张是我们的登录表,为user,另一张为校验表,里面的字符为token值,表的关系为一对一的形式,这里为什么要把token单独拿出来,而不是放在一张表里,因为token是一个校验字段,一般如果手段创建表,最好还是将token放在额外的一张表里好,这么做的好处第一是可以提高服务器效率,第二是也为爬虫能提高效率有关。

views.py:

from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication

from .models import Token
class TokenAuth(BaseAuthentication):
    def authenticate(self,request):
        token = request.GET.get("token")
        token_obj = Token.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed("验证失败123!")
        else:
            return token_obj.user.name,token_obj.token

上面是自定义的一个用户认证类,基本完成了认证的功能,这里需要注意的是在TokenAuth类中,类名是可以改的,只要是遵循了大驼峰命名法,其它随意,但方法名不能变,就和前面我介绍过的序列化组件一样,它内部源码已经固定了,所以如果不看里面的源码最好还是不要去加以修改,否则根据映射关系,找不到方法名那么就不再具有认证效果。

局部使用

views.py:

class AuthorModelView(viewsets.ModelViewSet):
    authentication_classes = [TokenAuth,]
    ...

另外这个继承的是我们自定义认证类,那么当程序执行时,它会自动去判断token对不对,也就是有没有登录,如果没有,那么会报401和403的错误。

  • 401 Unauthorized 未认证
  • 403 Permission Denied 权限被禁止

而如果我们不想自己写认证类,那么我们可以看到在我们写的认证类中,其实是继承了BaseAuthentication,也就是说restframework中帮我们封装好了认证权限组件,那么我们其实可以直接使用,在上一篇JWT中也介绍过认证总共有四个模块,分别为BaseAuthentication、SessionAuthentication、TokenAuthentication、JSONWebTokenAuthentication,具体的内容可以看上一篇博文。所以我们还可以这样设置:

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    ...

全局使用

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',   # 基本认证
        'rest_framework.authentication.SessionAuthentication',  # session认证
    )
}

这种就是全局的默认配置了,它会自动去调用内置的表,去查看有没有用户保存的记录,如果没有就会抛401或者403,如果我们配置了全局的,但有一些局部的类并不想用它的功能,因为并不是所有操作都需要认证,比如说我登录一下首页,那么我们就可以在类里面加一句:

    authentication_classes = []

表示不接受此认证功能。

使用认证类生成token

class LoginView(APIView):
    authentication_classes = []
    def post(self,request):

        name=request.data.get("name")
        pwd=request.data.get("pwd")
        user=User.objects.filter(name=name,pwd=pwd).first()
        res = {"state_code": 1000, "msg": None}
        if user:

            random_str=get_random_str(user.name)
            token=Token.objects.update_or_create(user=user,defaults={"token":random_str})
            res["token"]=random_str
        else:
            res["state_code"]=1001 #错误状态码
            res["msg"] = "用户名或者密码错误"

        import json
        return Response(json.dumps(res,ensure_ascii=False))

当我们利用postman登录验证正确后:

restframework(5):认证组件_第1张图片

否则会出现:

restframework(5):认证组件_第2张图片

这里通过判断输入值和数据库值是否匹配,然后再用update_or_create方法经过md5生成。那么我们就能拿到这个token进行验证获取登录操作。

使用token进行验证

views.py:

class AuthorModelView(viewsets.ModelViewSet):

    authentication_classes = [TokenAuth,]
    
    queryset = Author.objects.all()
    serializer_class = AuthorModelSerializers

url.py:

url(r'^login1',views.AuthorModelView.as_view({"get":"list"})),

如果我们不携带token值去发get请求,那么结果将会失败:
restframework(5):认证组件_第3张图片

而如果我们带上token,那么我们就能获取到所有的作者信息啦:
restframework(5):认证组件_第4张图片

可能你还会有很多的疑问,因为目前的东西并不好理解,那么下面就看看源码是怎么运作的。


认证源码解析

restframework(5):认证组件_第5张图片

这是我用亿图图示画的流程图,思维导图之前有尝试,但效果不好就删了。上图中的菱形是我们用户需要自己定义的内容,然后矩形是源码中的类名,圆形是方法名,大概整个认证的流程就是围绕着这三个类去周转的。(这里为啥是粉红色,因为选择基础的流程图默认就是。。。一般用Visio,不过发现都还好用)

View -> dispatch方法

前面的视图的博文中我们分析过了,整个视图源码,最精华的部分可能就是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
        #对原始request进行加工,丰富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications对象,])
        #获取原生request,request._request
        #获取认证类的对象,request.authticators
        #1.封装request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            #2.认证
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            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函数中的分发之前,还有上面这样一句代码,而认证、频率、验证组件都在这里面,那就是initial,我们此篇博文主要的逻辑也是在此。

initial初始化

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # 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

仅仅调用了request.user?认证逻辑为:如果user取到,就执行权限;取不到就pass掉。那么假如我们想要做一些逾越认证权限的功能,甚至也可以直接在类里调用这个函数,然后直接pass。

那么回归正题,这里就需要我们去找到request被调用的地方。

request的封装与初始化

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

这里也没啥好看的,返回了一大堆参数,但我们要找的user就在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

点进去后我们发现Request有个user方法,加 @property 表示调用user方法的时候不需要加括号“user()”,这就相当于将其转化成了静态方法,方便以后可以直接调用:request.user

_authenticate()和_not_authenticated函数

_authenticate()函数:

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        #循环认证类的所有对象
        #执行对象的authenticate方法
        for authenticator in self.authenticators:
            try:
                #执行认证类的authenticate方法
                #这里分三种情况
                #1.如果authenticate方法抛出异常,self._not_authenticated()执行
                #2.有返回值,必须是元组:(request.user,request.auth)
                #3.返回None,表示当前认证不处理,等下一个认证来处理
                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()

_not_authenticate()函数:

    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()   #AnonymousUser匿名用户
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()  #None
        else:
            self.auth = None

那么到这里,认证逻辑基本就都通了,当有这个用户时,会将user返回回去,但如果没有这个用户,那么将会是AnonymousUser 匿名用户。

另外,我们还可以看看restframework向Request类中多加的认证参数,self.get_authenticators(),当我们在视图里没有写认证方法时,它将会被调用。这个我在流程图里面也画了,与之对应的是我们在类中写的authentication_classes,只要我们用户中写了这个参数,那么它就会覆盖源码中的默认值,我们也可以来看一下源码流程:

    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 APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

总结

一直想写认证,现在终于大致过了一遍,不能说写得多通透,至少还有很多小细节点可能目前还没注意到,另外不知不觉中写了这么多,中途还发生过一些小插曲,就是电脑死机,而csdn又不像word能自动保存,本来是想权限和认证一起写的,那么就肯定没有单写考虑得那么多了,但就更能突出对比性。只能说各有优劣。然后我说的小细节就是,authenticate这个功能的后面,如果我们用户不给它赋值,那么它就会找到父类中的配置去,但这里我没有想下去了,找了一些别的博文看了下,发现最后会返回user和token的元组,还有我用亿图画的导图其实还能精简些,感觉有些丑了。。。算了,目前就那样吧。


参考文献:

  1. https://docs.djangoproject.com/en/1.11/topics/auth/default/
  2. https://blog.csdn.net/shanliangliuxing/article/details/7928940
  3. https://cloud.tencent.com/developer/article/1091441
  4. https://www.cnblogs.com/derek1184405959/p/8712206.html

你可能感兴趣的:(django)