RestFramework之認證組件


一,認證組件前戲准備

對於認證,我們一般有三種方式,即cookie, session,token,

  • cookie,是將信息存放在客戶端(瀏覽器上),信息不安全;
  • session,把信息放在服務器數據庫中,但是要是信息量較大,對服務器的壓力就會大大增加;
  • token采用每次用戶登陸后為其設置一個隨機字符串,即token值,用戶登陸之后,每次訪問都帶着這個token來訪問,服務端只需要驗證token值是否正確就可以,相對比較方便使用;

所以,我們使用token做認證;

1,准備user表和token表

class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    level = ((1, '普通'), (2, "vip"), (3, "svip"))
    user_level = models.IntegerField(choices=level)


class UserToken(models.Model):
    token = models.CharField(max_length=128)
    user = models.OneToOneField(to="User", on_delete=models.CASCADE)
models.py

2, 寫一個登陸CBV視圖

import uuid
from app01 import models
from rest_framework.views import APIView
from rest_framework.response import Response

class Login(APIView):

    def post(self, request):
        """
        0: 用戶名或者密碼錯誤!
        1: 成功
        2:其他異常
        :param request:
        :return:
        """
        res = {"code": 0, "msg": None, "user": None}
        try:
            username = request.data.get("username")
            password = request.data.get("password")
            user_obj = models.User.objects.filter(username=username, password=password).first()
            if user_obj:
                random_str = uuid.uuid4()
                # models.UserToken.objects.create(token=random_str, user=user_obj)  # 這樣是錯誤的,每次登陸都要去新建一條數據
                models.UserToken.objects.update_or_create(user=user_obj, defaults={"token": random_str})
                # 上面意思是: 根據user=user_obj,這個條件去篩選,如果能找到符合該條件的就更新該條數據token字段
                #              如果沒找到符合條件的,就去創建一條新數據;
                res["code"] = 1
                res["msg"] = "登陸成功!"
                res["user"] = username
                res["token"] = random_str
            else:
                res["msg"] = "用戶名或者密碼錯誤!"
        except Exception as e:
            res["code"] = 2
            res["msg"] = str(e)
        return Response(res)
views.py

二,restframework認證組件源碼解析

restframework框架的APIView這個類中做了很多事情,定義了as_view()方法,還定義了dispatch()方法,在dispatch()中對原來的django封裝的request對象進行了重新封裝,還為了我提供了認證,權限,頻率等Hooks;

源碼中得self.dispatch():  這里得self是我們得視圖類,在as_view()中的view函數中賦的值self = cls(**initkwargs);

    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 = 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
            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     

繼續 self.initial(): self是視圖類的實例化對象

    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) : self是視圖類

    def perform_authentication(self, request):
        request.user  # 從這里開始self就是新的request對象

繼續: request.user: self是新的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

繼續: self._authenticate()

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        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()

可以看到該方法中是在循環self.authenticatiors,這里的self是request對象,那我們就需要去看看實例化Request()類的時候,傳的參數是什么?

    def initialize_request(self, request, *args, **kwargs):  # 該方法是我們的視圖類對象self調用的
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  # 這里的self就是我們的視圖類了,
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

繼續 self.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,生成的一個個的對象

走到這里我們可以清楚的看到,流程基本和我們的parse_class,一模一樣,都是先從自己的視圖類找 authentication_classes,找不到再去全局settings中找,再找不到才使用默認的;

默認的是:

'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ),

也就是說要使用restframework的認證組件,我們就需要自己寫一個類,放在視圖類的authentication_classes中,或者在全局settings中的 REST_FRAMEWORK 中配置;

三,使用restframework的認證組件

使用restframework的認證組件,上文已經提到,我們需要自己寫一個類,放到視圖類的authentication_classes中,或者配置到全局settings;

那么我們寫的這個類要寫什么東西呢?看看源碼:

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        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()

源碼告訴我們,我們需要寫一個authenticate()方法,該方法要么返回None,要么返回一個元組,如果拋出APIView錯誤,或者沒有return關鍵字,都會認證失敗,返回拋出錯誤的信息;

有個小坑:再寫了多個認證類是,要注意,如果return的是元組,需要在那個數組中的最后一個寫return,放在前面就導致直接return,不會繼續循環其他認證類了

所以在我們寫的認證類中:

當然,他還要求我們寫的認證類中,還要有 authenticate_header()方法(雖然沒啥用,但是不寫會報錯),所以我們可以通過繼承他的 BaseAuthentication類,再去覆蓋他的authenticate()方法,就可以只寫這個方法了。

認證類:

from app01 import models
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed


class UserAuth(BaseAuthentication):
    def authenticate(self, request):
        # 我們模仿get請求頁面
        token = request.query_params.get("token")  # 同request.GET.get("token")
        # 在token表中查找有么有這次請求攜帶的token值
        user_token_obj = models.UserToken.objects.filter(token=token).first()
        if user_token_obj:
            # 如果有值,說明是 正常用戶
            return user_token_obj.user, user_token_obj.token
            # 返回 當前的用戶對象,和當前的token值,這樣源碼就會幫我們賦值給request對象了,我們在后面的request中就可以使用了
        else:
            raise AuthenticationFailed("認證失敗!")

視圖類:

class BookView(generics.GenericAPIView, ListModelMixin, CreateModelMixin):
    queryset = models.Book.objects.all()
    serializer_class = BookSerializer

    authentication_classes = [UserAuth]

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

或者把上面的注釋掉,配置在全局setttings中:

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'app01.utils.auth_class.UserAuth',
    ),

}

 不帶token訪問時:

帶上token:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM