Django REST framework之認證

1. 自定義認證

有些API不需要用戶登錄就可以訪問,但是有些需要用戶登錄纔可以訪問。Django REST framework中內置認證組件,可以實現需要用戶登錄纔可以訪問API的功能。藉助內置認證組件,可以方便地自定義認證規則:

models.py

from django.db import models

# Create your models here.
class User(models.Model):
    name = models.CharField(max_length=32, unique=True)
    pwd = models.CharField(max_length=64)
    user_type_choices = (
        (1, '普通用戶'),
        (2, 'VIP'),
        (3, 'SVIP')
    )
    user_type = models.IntegerField(choices=user_type_choices)

class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to='User',on_delete=models.CASCADE)

在這裏插入圖片描述
寫一些數據,用於登錄認證測試:
在這裏插入圖片描述
setting.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'drftest',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': 'localhost',
        'PORT': '3306'
    },
    'mysql': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

views.py:

import hashlib, time
from rest_framework.views import APIView, exceptions
from .models import User, UserToken
from django.http import JsonResponse

# Create your views here.
def md5(name):
    obj = hashlib.md5(bytes(name, encoding='utf-8'))
    ctime = str(time.time())
    obj.update(bytes(ctime, encoding='utf-8'))
    return obj.hexdigest()

class Authenticate:
    def authenticate(self, request):
        token = request._request.GET.get('token')
        print(token)
        token_obj = UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗!')
        # 在rest framework內部會將整個兩個字段賦值給request,共後續操作使用
        # 有三種返回值,None:表示不管;異常:沒有通過認證;元組:返回下面兩個元素,一個給request.use,一個給request.auth
        return (token_obj.user, token_obj)  # (request.user,request.auth)

    def authenticate_header(self, request):
        pass

class AuthView(APIView):
    def post(self, request, *args, **kwargs):
        """
        用戶登錄成功後,返回根據時間輟生成的token,每次登錄的時間不同,每次生成的token也不同,都被記錄到token表中用於與每次請求帶着的token進行對比。如果對比成功,則認證成功,是允許訪問的。
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        ret = {'code': 1000, 'msg': None}
        try:
            # 需要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果一樣
            print(instance)  # User object (1),加不加all()結果一樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

class OrderView(APIView):
    # 列表中有寫認證類則需要認證,使用自定義的Authenticate類來認證
    authentication_classes = [Authenticate, ]

    def get(self, request, *args, **kwargs):
        # request.user
        # request.auth
        self.dispatch
        order_dict = {
            1: {
                'name': "thanlon",
                'age': 24,
                'gender': '男',
            },
            2: {
                'name': "kiku",
                'age': 26,
                'gender': '女',
            },
        }
        # token = request._request.GET.get('token')
        ret = {'code': 1000, "msg": None, 'data': None}
        try:
            ret['data'] = order_dict
        except Exception as e:
            pass
        return JsonResponse(ret)

登錄時生成的token:
在這裏插入圖片描述
用於用戶登錄成功生成token的類AuthView不需要認證,OrderView類需要認證,如果不帶token訪問這個接口會返回失敗的認證:
在這裏插入圖片描述
帶着這token來訪問這個接口,注意這裏從url中獲取的,要把token放在url上,不要放到請求頭髮送過去。結果發現可以訪問到請求的數據:
在這裏插入圖片描述
也可以放到請求頭中發過去,認證類獲取token的時候要到請求頭中獲取。

2. 認證流程

想要更好地使用認證組件,不得不研究和學習下認證組件的實現原理:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
認證可以加多個,一般不會使用到多個認證。列表中的認證類中從第一個開始,如果第一個認證沒有做處理,返回None,則交給下一個認證處理:

import hashlib, time
from rest_framework.views import APIView, exceptions
from .models import User, UserToken
from django.http import JsonResponse

# Create your views here.
def md5(name):
    obj = hashlib.md5(bytes(name, encoding='utf-8'))
    ctime = str(time.time())
    obj.update(bytes(ctime, encoding='utf-8'))
    return obj.hexdigest()

class Authenticate:
    def authenticate(self, request):
        token = request._request.GET.get('token')
        print(token)
        token_obj = UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗!')
        # 在rest framework內部會將整個兩個字段賦值給request,共後續操作使用
        return (token_obj.user, token_obj)  # (request.name,request.auth)

    def authenticate_header(self, request):
        pass

class FirstAuthenticate:
    def authenticate(self, request):
        pass

    def authenticate_header(self, request):
        pass

class AuthView(APIView):
    def post(self, request, *args, **kwargs):
        print(md5('thanlon'))
        ret = {'code': 1000, 'msg': None}
        try:
            # 需要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果一樣
            print(instance)  # User object (1),加不加all()結果一樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

class OrderView(APIView):
    # 需要認證,使用自定義的Authenticate類來認證
    authentication_classes = [FirstAuthenticate, Authenticate, ]

    def get(self, request, *args, **kwargs):
        # request.name
        # request.auth
        self.dispatch
        order_dict = {
            1: {
                'name': "thanlon",
                'age': 24,
                'gender': '男',
            },
            2: {
                'name': "kiku",
                'age': 26,
                'gender': '女',
            },
        }
        # token = request._request.GET.get('token')
        ret = {'code': 1000, "msg": None, 'data': None}
        try:
            ret['data'] = order_dict
        except Exception as e:
            pass
        return JsonResponse(ret)

流程概述:

- dispatch
	- 封裝request
		- 獲取定義的認證類(全局或者局部),通過列表生成式創建對象
	- initial
		- perform_authentication
			request.user
				內部循環認證類執行authenticate方法
3. 全局配置認證

如果我們不使用自己的認證類,默認使用Django REST framework的認證類,路徑在配置文件中。源碼中有體現:
在這裏插入圖片描述
在這裏插入圖片描述
加下來可以這樣來配置全局的認證類。在app目錄下創建用於存放認證類的 auth,py 文件(認證的類不要寫在views中,否則可能引用出現問題),然後在 settings.py 的認證配置項指向這個文件:

settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['app.auth.FirstAuthenticate', 'app.auth.Authenticate', ]
}

auth.py:

from rest_framework.views import exceptions
from .models import UserToken

class FirstAuthenticate:
    def authenticate(self, request):
        pass

    def authenticate_header(self, request):
        pass


class Authenticate:
    def authenticate(self, request):
        token = request._request.GET.get('token')
        print(token)
        token_obj = UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗!')
        # 在rest framework內部會將整個兩個字段賦值給request,共後續操作使用
        return (token_obj.user, token_obj)  # (request.name,request.auth)

    def authenticate_header(self, request):
        pass

這樣配置之後全部請求的方法都需要認證,但是有些是需要認證的,比如登錄的方法,也需要認證:
在這裏插入圖片描述
只需要在類中把authentication_classes設置爲空列表即可:

class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
        print(md5('thanlon'))
        ret = {'code': 1000, 'msg': None}
        try:
            # 需要以form-data的方式提交
            name = request._request.POST.get('name')
            pwd = request._request.POST.get('pwd')
            instance = User.objects.filter(name=name, pwd=pwd).first()  # User object (1),
            print(type(instance))  # <class 'app.models.User'>,加不加all()結果一樣
            print(instance)  # User object (1),加不加all()結果一樣
            if not instance:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            else:
                token = md5(name=name)
                UserToken.objects.update_or_create(user=instance, defaults={'token': token})
                ret['token'] = token
        except Exception as e:
            ret['code'] = 1001
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

正常訪問,不需要認證:
在這裏插入圖片描述

4. 匿名用戶配置

可以根據源碼,來配置匿名用戶:
在這裏插入圖片描述
在這裏插入圖片描述
settings.py:

REST_FRAMEWORK = {
    # 'DEFAULT_AUTHENTICATION_CLASSES': ['app.auth.FirstAuthenticate', 'app.auth.Authenticate', ]
    'DEFAULT_AUTHENTICATION_CLASSES': ['app.auth.FirstAuthenticate', ],  # FirstAuthenticate中什麼也沒有做
    # 'UNAUTHENTICATED_USER': lambda x: '匿名用戶',
    'UNAUTHENTICATED_USER': None,  # request.user = None,默認是AnonymousUser
    'UNAUTHENTICATED_TOKEN': None  # request.auth = None
}
5. 內置基本認證

Django REST framework中內置了很多內部認證類,
在這裏插入圖片描述
導入這些認證類的方式:

from rest_framework.authentication import BaseAuthentication,BasicAuthentication, SessionAuthentication,TokenAuthentication, RemoteUserAuthentication

BaseAuthentication類有兩種方法,所有的認證類必須繼承這個類:

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

自定義認證類的時候,必須繼承BaseAuthentication,其它的認證類中BasicAuthentication是瀏覽器對用戶名和密碼進行base64加密,

HTTP_AUTHORIZATION:basic base64(用戶名和密碼)

然後放到請求頭裏面發送給服務端。服務端接收數據後進行處理,之後做一系列的校驗:
在這裏插入圖片描述
剩下的認證則是基於Django的session和token等實現的認證。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章