1. 程式人生 > >Django框架(二十)—— Django rest_framework-認證元件

Django框架(二十)—— Django rest_framework-認證元件

目錄

Django rest_framework-認證元件

一、什麼是認證

只有認證通過的使用者才能訪問指定的url地址,比如:查詢課程資訊,需要登入之後才能檢視,沒有登入,就不能檢視,這時候需要用到認證元件

二、利用token記錄認證過的使用者

1、什麼是token

token是服務端生成的一串字串,以作客戶端進行請求的一個令牌,當第一次登入後,伺服器生成一個Token便將此Token返回給客戶端,以後客戶端只需帶上這個Token前來請求資料即可,無需再次帶上使用者名稱和密碼。session的儲存是需要空間

的,session的傳輸一般都是通過cookie來傳輸,或url重寫的方式。
token在伺服器時可以不用儲存使用者資訊的,token傳遞的方式也不限於cookie傳遞,token也可以儲存起來

2、token的原理

當第一次登入認證過後,就會返回一個token到前臺,前臺之後傳送請求,就會帶上這個token字串,

3、cookie、session、token的區別

cookie是儲存在瀏覽器,以key:value的形式傳遞到伺服器認證使用者,使用者名稱密碼可能裸露,不安全
session是儲存在伺服器,產生隨機字串與使用者資訊對應,將隨機字串放在cookie中。伺服器會儲存一份,可能儲存到快取/資料庫/檔案。
token傳送請求的物件可以是瀏覽器,也可以是移動端。伺服器不需要記錄任何東西,每次都是一個無狀態的請求,每次都是通過解密來驗證是否合法

三、drf的認證元件

# 使用者資訊表
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)

# 使用者token
class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to=UserInfo)

1、基本使用

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

# 定義一個認證類
class LoginAuth(BaseAuthentication):
    # 重寫authenticate方法
    def authenticate(self, request):
        # 從路由中獲取token,從request物件中獲取,如果token放在header中
        # query_params 是Request封裝的原生request.GET
        token = request.query_params.get('token')
        ret = models.UserToken.objects.filter(token=token).first()
        if ret:
            # 能查到,說明認證通過,返回空,或者返回當前的使用者物件
            # ret.user就是當前登入使用者物件,一旦retrun了,後面的認證類都不執行了
            return ret.user,ret
        # 如果查不到,丟擲異常
        raise APIException('使用者認證失敗')
        
# view 層
from rest_framework.views import APIView
# 獲取隨機字串——token
def get_token(name):
    md = hashlib.md5()
    md.update(str(name).encode('utf-8'))
    return md.hexdigest()
# 使用者登入
class Login(APIView):
    def post(self, request, *args, **kwargs):
        response = {'status': 100, 'msg': '登入成功'}
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        ret = models.UserInfo.objects.filter(name=name, pwd=pwd).first()
        if ret:
            token = get_token(name)
            # 一旦使用者資訊校驗通過,就產生一個token儲存在Token表中
            models.UserToken.objects.create(token=token, user=ret)
            response['token'] = token
        else:
            response['status'] = 101
            response['msg'] = '使用者名稱或密碼錯誤'
        return JsonResponse(response, safe=False)

# 檢視所有圖書資訊    
class Book(APIView):
    # 指定authentication_classes,會迴圈列表例項化,進行認證
    # 該方法是區域性使用認證
    authentication_classes = [UserAuth.UserAuth, ]

    def get(self, request, *args, **kwargs):
        response = {'status': 100, 'msg': '查詢成功'}
        ret = models.Book.objects.all()
        ser = MySerializer.BookSerializer(instance=ret, many=True)
        response['data'] = ser.data
        return JsonResponse(response, safe=False)

2、全域性使用、區域性使用、區域性禁用認證

(1)全域性使用

  • 在settings檔案中配置,配置完以後,就無需在檢視類中寫,已經是所有檢視類都需要認證
  • 必須為REST_FRAMEWORK,key值必須為DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK={
    'DEFAULT_AUTHENTICATION_CLASSES':['app01.MyAuth.LoginAuth',],
}

(2)區域性使用

在需要使用的檢視類中寫,只對當前檢視類起認證作用,重新定義authentication_classes

class Book(APIView):
    # 該方法是區域性使用認證
    authentication_classes = [UserAuth.UserAuth, ]

    def get(self, request, *args, **kwargs):
        response = {'status': 100, 'msg': '查詢成功'}
        ret = models.Book.objects.all()
        ser = MySerializer.BookSerializer(instance=ret, many=True)
        response['data'] = ser.data
        return JsonResponse(response, safe=False)

(3)區域性禁用

在配置過全域性認證以後,有些檢視類不需要認證,可以區域性禁用認證,只需將authentication_classes定義為空列表即可。例如:登入檢視,不應該有認證,就可以區域性禁用

# settings中
REST_FRAMEWORK={
    'DEFAULT_AUTHENTICATION_CLASSES':['app01.MyAuth.LoginAuth',],
}

# view的檢視類中
class Book(APIView):
    # 該方法是區域性使用認證
    authentication_classes = []

    def get(self, request, *args, **kwargs):
        response = {'status': 100, 'msg': '查詢成功'}
        ret = models.Book.objects.all()
        ser = MySerializer.BookSerializer(instance=ret, many=True)
        response['data'] = ser.data
        return JsonResponse(response, safe=False)

四、原始碼分析

as_view ----------> view -------------> dispatch -------> Request包裝新的request ------> 認證、許可權、頻率 --------> 根據請求方式分發到不同的方法

url(r'books/',views.Book.as_view())

1、Book中沒有as_view

2、APIView的as_view

class APIView(View):
    
    @classmethod
    # cls 是 Book類
    def as_view(cls, **initkwargs):
            
        # view = super(APIView, Book).as_view(**initkwargs)
        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)

3、view = super(APIView, cls).as_view(**initkwargs) ---------------------> View中的as_view

class View(object):
    
    @classonlymethod
    # cls====> Book
    def as_view(cls, **initkwargs):

        def view(request, *args, **kwargs):
            # 例項化產生一個book物件
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            # 調dispatch方法
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

4、return self.dispatch(request, *args, **kwargs) ----------------> dispatch

self====> Book物件,一層層找dispatch

APIView中找到dispatch

class APIView(View):
    
    def dispatch(self, request, *args, **kwargs):

        self.args = args
        self.kwargs = kwargs
        
        # (a)初始化request,就是通過Request類來包裝原生request,得到包裝後的request
        request = self.initialize_request(request, *args, **kwargs)
        # 從現在開始request就是包裝後的request
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # (b) 認證、許可權、頻率
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            # http_method_names表示列表['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
            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
    
    

(a)request = self.initialize_request(request, *args, **kwargs) 包裝 request

self 是Book物件

class APIView(View):
    # 預設的認證列表類
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)
        # (a-b)例項化初始化產生新的request物件
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),  # 認證類例項化產生的物件的列表
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
(a------1)return Request( ··· ) ----------> Request類初始化
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

(b)self.initial(request, *args, **kwargs) -----> 認證、許可權、頻率

    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
        # (b------1) 認證
        self.perform_authentication(request)
        # (b------2)許可權
        self.check_permissions(request)
        # 頻率
        self.check_throttles(request)
(b------1) 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

(b------1------1) 呼叫request的user方法,request是Request例項化產生的物件

class Request(object):
    @property
    def user(self):
        
        # 這裡的self是request
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()   # 具體方法如下
        return self._user

(b------1------1-------1) self._authenticate()

class Request(object): 
    def _authenticate(self):
        # 這裡的self是request
        # self.authenticators就是在Request例項化的時候,傳過來的認證類的物件的列表
        # 即:[auth() for auth in self.authentication_classes]
        for authenticator in self.authenticators:
            try:
                # 重寫的就是這個authenticate()方法,
                # 兩個引數,第一個是物件本身(自動傳);第二個是這裡的self,就是request
                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()