一,認證組件前戲准備
對於認證,我們一般有三種方式,即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)
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)
二,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: