1. 程式人生 > >DAY99 - Rest Framework(四)- 認證元件和許可權元件

DAY99 - Rest Framework(四)- 認證元件和許可權元件

一、認證元件

1.使用

# 模型層
class User(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)


class UserToken(models.Model):
    user = models.OneToOneField(to='User')
    token = models.CharField(max_length=64)
#myAuth.py
from app01 import models
from rest_framework.exceptions import APIException
from rest_framework.authentication import BaseAuthentication

class Auth(BaseAuthentication):
    def authenticate(self,request):
        # 包裝後的request物件,請求來的所有東西都能拿出來
        token = request.GET.get('token')
        ret = models.UserToken.objects.filter(token=token).first()
        # 如果有值,說明登入過了,而且帶的隨機字串也是正確的
        # 如果認證通過,需要返回東西,如果認證不通過,要拋異常
        if ret:
            # 返回一個元組如果返回None,就會是一個匿名使用者
            return ret.user,ret
        else:
            # 如果沒有值,拋異常
            raise APIException('您沒有登入')
from app01.mySer import *
import uuid

# 登陸檢視
class Login(APIView):
    def post(self, request):
        response = {'status': 200, 'msg': '登入成功'}
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        user = models.User.objects.filter(name=name, pwd=pwd).first()
        if user:
            token = uuid.uuid4()
            # 登陸成功後,存入token表
            models.UserToken.objects.create(token=token, user=user)
            response['token'] = token
        else:
            response['status'] = 201
            response['msg'] = '新增失敗'
        return JsonResponse(response, safe=False)
# 區域性使用,在所需的視圖裡區域性使用
class Books(APIView):
    # 登入後才能操作
    authentication_classes=[Auth,]
    def get(self, request):
        .......
    def post(self, request):
        .......
class BooksDetail(APIView):
    authentication_classes = [Auth, ]
    def get(self, request):
        .......
    def post(self, request):
        .......
        
        
# 全域性使用
# setting.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.my_Auth.Auth']
}

# 全域性使用後區域性禁用
# 在所屬的試圖裡寫一個空
authentication_classes=[]

2.原始碼分析

# 第一步
# APIView類
def dispatch(self, request, *args, **kwargs):
    ........
    # 重點是這個,這是認證、頻率相關的
    self.initial(request, *args, **kwargs)
    ........
# 第二步
# APIView類
def initial(self, request, *args, **kwargs):
    ........
    # 這個就是認證
    self.perform_authentication(request)
    
    self.check_permissions(request)
    self.check_throttles(request)
# 第三步
# APIView類
def perform_authentication(self, request):
    # 這個request是已經封裝好後的Request的物件
    request.user
# 第四步
# Request類
@property
def user(self):
    if not hasattr(self, '_user'):
        with wrap_attributeerrors():
            self._authenticate()
    return self._user
# 第五步
# Request類
def _authenticate(self):
    # 從下面回到這裡,就可以知道
    # self.authenticators=authentication_classes
    # 拿上面的例子舉例
    # self.authenticators=[Auth, ];authenticator就是Auth
    for authenticator in self.authenticators:
        try:

# 注意:authenticator.authenticate(self)中的self,由於是在Request類裡,所以這個self就是Request例項化的物件request;
# 所以:authenticator.authenticate(self)=Auth.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
            # user_auth_tuple:authenticator.authenticate(self)的返回值,是個元組
            # 這就是一個元組的解壓
            # self.user:request.user
            # self.auth:request.auth
            self.user, self.auth = user_auth_tuple
            return

   self._not_authenticated()


# self.authenticators的來歷
# APIView類
# self.authenticators 是Request例項化的時候傳進來的引數self.get_authenticators()
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
    )


# self.get_authenticators()的來歷
# APIView類
def get_authenticators(self):
    # self.authentication_classes
    # 從子類找驗證類:authentication_classes = [Auth, ]
    # 從父類APIView裡找驗證類:authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    # 找到之後,迴圈並加上()執行
    return [auth() for auth in self.authentication_classes]
    

3.不依靠資料庫的token驗證

import hashlib
# 設定token
def set_token(id, salt='1'):
    md5 = hashlib.md5()
    md5.update(str(id).encode('utf-8'))
    md5.update(salt.encode('utf-8'))
    return md5.hexdigest() + '|' + str(id)

# 校驗token
def check_token(token, salt='1'):
    tk = token.split('|')
    md5 = hashlib.md5()
    md5.update(str(tk[-1]).encode('utf-8'))
    md5.update(salt.encode('utf-8'))
    if tk[0] == md5.hexdigest():
        return True
    else:
        return False
class Auth(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get('token')
        # 如果GET裡沒有token,check_token裡的split方法就會報錯
        # 所以這裡try一下異常
        try:
            ret = common.check_token(token)
            if ret:
                return
            else:
                # 拋異常
                raise 
        except Exception as e:
            raise AuthenticationFailed('認證失敗')
class Login(APIView):
    def post(self, request):
        response = {'status': 100, 'msg': '登陸成功', 'token': None}
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        user = models.User.objects.filter(name=name, pwd=pwd).first()
        if user:
            # 傳入使用者id獲得對應的token
            token = common.set_token(user.pk)
            response['token'] = token
        else:
            response['status'] = 200
            response['msg'] = '登入失敗'
        return JsonResponse(response, safe=False)

class Book(APIView):
    authentication_classes = [myAuthentication.Auth, ]
    def get(self, request):
        response = {'status': 100, 'msg': '檢視成功', 'data': None}
        books = models.Book.objects.all()
        ret = mySerializers.BookSerializers(books,many=True)
        response['data']=ret.data
        return JsonResponse(response,safe=False)

4.順序

認證類使用順序:先用檢視類中的驗證類,再用settings裡配置的驗證類,最後用預設的驗證類

二、許可權元件

1.簡單使用

from rest_framework.permissions import BasePermission

class VipPermission(BasePermission):
    message = '不是會員,檢視不了'

    def has_permission(self, request, view):
        user_type = request.user.user_type
        if user_type == '1':
            return True
        else:
            return False
class Book(APIView):
    # 區域性使用
    permission_classes = [myPermission.VipPermission, ]

    def get(self, request):
        response = {'status': 100, 'msg': '檢視成功', 'data': None}
        books = models.Book.objects.all()
        ret = mySerializers.BookSerializers(books, many=True)
        response['data'] = ret.data
        return JsonResponse(response, safe=False)
    
    
# 全域性使用
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES':['app01.myPermission.VipPermission']
}

# 全域性使用中區域性禁用
class Book(APIView):
    permission_classes = []

2.原始碼分析

# 第一步
# APIView類
def dispatch(self, request, *args, **kwargs):
    ........
    # 重點是這個,這是認證、頻率以及許可權相關的
    self.initial(request, *args, **kwargs)
    ........
# 第二步
# APIView類
def initial(self, request, *args, **kwargs):
    ........
    self.perform_authentication(request)
    # 這就是 許可權方法
    self.check_permissions(request)
    self.check_throttles(request)
# 第三步
# APIView類
def check_permissions(self, request):
    # 這裡的原理與認證類一致
    for permission in self.get_permissions():
        # 有一點不同的是has_permission(request, self)中的self是APIView的self
        if not permission.has_permission(request, self):
            self.permission_denied(request, message=getattr(permission, 'message', None)
            )
            
            
# self.get_permissions()的來歷
def get_permissions(self):
   return [permission() for permission in self.permission_classes]

3.順序

許可權類使用順序:先用檢視類中的許可權類,再用settings裡配置的許可權類,最後用預設的許可權類