django-rest-framework原始碼分析2—認證(Authentication)原始碼解析

程式猿小劉LeooO發表於2020-12-15

什麼是認證(Authentication)

通俗地講就是驗證當前使用者的身份,證明 “你是你自己”(比如:你每天上下班打卡,都需要通過指紋打卡,當你的指紋和系統裡錄入的指紋相匹配時,就打卡成功)

網際網路中的認證:

  • 使用者名稱密碼登入
  • 郵箱傳送登入連結
  • 手機號接收驗證碼
  • 只要你能收到郵箱 / 驗證碼,就預設你是賬號的主人

傳統認證方式

# models
class UserInfo(models.Models):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    
class UserToken(models.Models):
    user = models.OneToOneField(to='UserInfo')
    token = models.CharField(max_length=64)
# views
def md5(user):
    import hashlib
    import time
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,enceding='utf-8'))
    m.update(bytes(ctime,enceding='utf-8'))
    return m.hexdigest()
    
class AuthView(APIView):
    def post(self,request,*args,**kwargs):
        ret = {"code":'1000','msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = reuqest._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=name,password=pwd).first()
            if not obj:
                ret['code']='1001'
                ret['msg']='使用者名稱祕密錯誤'
            token = md5(user)
            models.UserToken.objects.update_or_create(user=obj,default={'token':token})
            ret['token']=token
        except Excepion as e:
            ret['code']='1002'
            ret['msg']='請求異常'
        return JsonResponse(ret)

原始碼分析

#認證中的基類,主要實現authenticateauthenticate_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

#基於使用者名稱和密碼的認證

class BasicAuthentication(BaseAuthentication):

#基於session的認證

class SessionAuthentication(BaseAuthentication):

#基於token的認證

class TokenAuthentication(BaseAuthentication): ---

#遠端使用者認證


class RemoteUserAuthentication(BaseAuthentication):
    """
    REMOTE_USER authentication.

    To use this, set up your web server to perform authentication, which will
    set the REMOTE_USER environment variable. You will need to have
    'django.contrib.auth.backends.RemoteUserBackend in your
    AUTHENTICATION_BACKENDS setting
    """

認證流程

authentication_classes
請求到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 = self.initialize_request(request, *args, **kwargs)
      self.request = request
      self.headers = self.default_response_headers  # deprecate?
      try:
          self.initial(request, *args, **kwargs)

呼叫self.initial()

def initial(self, request, *args, **kwargs):
    self.perform_authentication(request)

呼叫 perform_authentication() 返回request.user

def perform_authentication(self, request):
     request.user  

**在dispatch中initialize_request封裝了Request **

def initialize_request(self, request, *args, **kwargs):
    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
    )

Request 有方法註解 user 呼叫_authenticate 迴圈認證物件 執行方法

@property
def user(self):
    if not hasattr(self, '_user'):
        with wrap_attributeerrors():
            self._authenticate()
    return self._user

def _authenticate(self):
    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()

使用方法

  • 全域性使用

自定義的認證類可以通過全域性設定 放在setting檔案中 原始碼中會讀取setting中的

REST_FRAMEWORK={
       'DEFAULT_AUTHENTICATION_CLASSES'=('自定義的認證類'),
       'UNAUTHENTICATED_USER':None,
       'UNAUTHENTICATED_TOKEN':None
   }   
  • 區域性使用(檢視級別的認證)

內建的認證類 rest_framework.authentication BasicAuthentication

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuth(BaseAuthentication):
	def authenticate(self, request):
		if request.method in ["POST", "PUT", "DELETE"]:
			request_token = request.data.get("token", None)
			if not request_token:
				raise AuthenticationFailed('缺少token')
			token_obj = models.Token.objects.filter(token_code=request_token).first()
			if not token_obj:
				raise AuthenticationFailed('無效的token')
			return token_obj.user.username, None
		else:
			return None, None

繼承 BasicAuthentication 建立自定義的認證類 實現 authenticate方法
新增到檢視類的 authentication_classes

class CommentViewSet(ModelViewSet):
	queryset = models.Comment.objects.all()
	serializer_class = app01_serializers.CommentSerializer
	authentication_classes = [MyAuth, ]

相關文章