1. 程式人生 > >DRF Django REST framework 之 認證元件(五)

DRF Django REST framework 之 認證元件(五)

引言

很久很久以前,Web站點只是作為瀏覽伺服器資源(資料)和其他資源的工具,甚少有什麼使用者互動之類的煩人的事情需要處理,所以,Web站點的開發這根本不關心什麼人在什麼時候訪問了什麼資源,不需要記錄任何資料,有客戶端請求,我即返回資料,簡單方便,每一個http請求都是新的,響應之後立即斷開連線。

而如今,網際網路的世界發生了翻天覆地的變化,使用者不僅僅需要跟其他使用者溝通交流,還需要跟伺服器互動,不管是論壇類、商城類、社交類、門戶類還是其他各類Web站點,大家都非常重視使用者互動,只有跟使用者互動了,才能進一步留住使用者,只有留住了使用者,才能知道使用者需求,知道了使用者需求,才會產生商機,有了使用者,就等於有了流量,才能夠騙到…額…是融到錢,有了資金企業才能繼續發展,可見,使用者互動是非常重要的,甚至可以說是至關重要的一個基礎功能。

而談到使用者互動,則必須要談到我們今天所要學習的知識點,認證、許可權和頻率。首先我們來看看認證。

認證元件

使用token

大部分人都知道cookie和session這兩種方式可以儲存使用者資訊,這兩種方式不同的是cookie儲存在客戶端瀏覽器中,而session儲存在伺服器中,他們各有優缺點,配合起來使用,可將重要的敏感的資訊儲存在session中,而在cookie中可以儲存不太敏感的資料。

而token稱之為令牌。cookie、session和token都有其應用場景,沒有誰好誰壞,不過開發資料介面類的Web應用,目前用token還是比較多的。

token認證的大致步驟是這樣的:

  • 使用者登入,伺服器端獲取使用者名稱密碼,查詢使用者表,如果存在該使用者且第一次登入(或者token過期),生成token,否則返回錯誤資訊
  • 如果不是第一次登入,且token未過期,更新token值

定義 url 

from django.urls import path, re_path

from DrfOne import views

urlpatterns = [

    path("books/", views.BookView.as_view({
        "get": "list",
        "post": "create",
    })),
    re_path('books/(?P<pk>\d+)/', views.BookView.as_view({
        'get': 'retrieve',
        'put': 'update',
        'delete': 'destroy'
    })),

    # 登陸
    path('login/', views.LoginView.as_view()),
]

建立兩個 model ,如下所示:

from django.db import models

class UserInfo(models.Model): username = models.CharField("姓名", max_length=32) password = models.CharField("密碼", max_length=32) age = models.IntegerField("年齡") gender = models.SmallIntegerField("性別", choices=((1, "男"), (2, "女")), default=1) user_type_entry = ((1, "普通使用者"), (2, "VIP"), (3, "SVIP")) user_type = models.SmallIntegerField("使用者級別", choices=user_type_entry) def __str__(self): return self.username class UserToken(models.Model): user = models.ForeignKey(to="UserInfo", on_delete=models.CASCADE) token = models.CharField(max_length=128)

 post 方法介面,檢視類如下所示:

from django.http import JsonResponse

from rest_framework.views import APIView

from DrfTwo.models import UserInfo, UserToken


class LoginView(APIView):
    def post(self, request):
        # 定義返回資訊
        ret = dict()
        try:
            # 定義需要的資訊
            fields = {"username", "password"}
            # 定義一個使用者資訊字典
            user_info = dict()
            # 判斷fields是否是request.data的子集
            if fields.issubset(set(request.data.keys())):
                for key in fields:
                    user_info[key] = request.data.get(key)

            user_instance = UserInfo.objects.filter(**user_info).first()
            # 使用者驗證
            if user_instance:
                # 自定義generate_token()方法,獲取token值,在後面
                access_token = generate_token()
                # 使用者登陸成功,建立token,token存在更新token, defaults為更新內容
                UserToken.objects.update_or_create(user=user_instance, defaults={
                    "token": access_token
                })
                ret["status_code"] = 200
                ret["status_message"] = "登入成功"
                ret["access_token"] = access_token
                ret["user_role"] = user_instance.get_user_type_display()

            else:
                ret["status_code"] = 201
                ret["status_message"] = "登入失敗,使用者名稱或密碼錯誤"
        except Exception as e:
            ret["status_code"] = 202
            ret["status_message"] = str(e)


        return JsonResponse(ret)

簡單寫了個獲取隨機字串的方法用來生成token值:

import uuid


def generate_token():
    random_str = str(uuid.uuid4()).replace("-", "")
    return random_str

以上就是token的簡單生成方式,當然,在生產環境中不會如此簡單,關於token也有相關的庫,然後在構造幾條資料之後,可以通過POSTMAN工具來建立幾個使用者的token資訊。

 

接下來,如何對已經登入成功的使用者實現訪問授權呢?也就是說,只有登入過的使用者(有token值)才能訪問特定的資料,該DRF的認證元件出場了

認證元件的使用

首先,新建一個認證類,之後的認證邏輯就包含在這個類裡面:

# 1.定義認證類
class UserAuth(object):
    # 認證邏輯
    def authenticate(self, request):
        user_token = request.GET.get('token')

        token_object = UserToken.objects.filter(token=user_token).first()
        if token_object:
            return token_object.user.username, token_object.token
        else:
            raise APIException("認證失敗")

實現方式看上去非常簡單,到 token 表裡面檢視 token 是否存在,然後根據這個資訊,返回對應資訊即可,然後,在需要認證通過才能訪問的資料介面裡面註冊認證類即可:

from rest_framework.viewsets import ModelViewSet
from rest_framework.exceptions import APIException

from DrfOne.models import Book, UserToken
from DrfOne.drf_serializers import BookSerializer


# 1.定義認證類
class UserAuth(object):
    # 認證邏輯
    def authenticate(self, request):
        user_token = request.GET.get('token')

        token_object = UserToken.objects.filter(token=user_token).first()
        if token_object:
            return token_object.user.username, token_object.token
        else:
            raise APIException("認證失敗")


class BookView(ModelViewSet):
    # 2.指定認證類,固定寫法
    authentication_classes = [UserAuth]
    # 獲取資料來源, 固定寫法
    queryset = Book.objects.all()
    # 序列化類, 固定寫法
    serializer_class = BookSerializer

序列類 BookSerializer 

from rest_framework import serializers

from DrfOne import models


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Book
        fields = "__all__"

        extra_kwargs = {
            # 僅寫
            "publish": {'write_only': True},
            "authors": {'write_only': True},
        }

    publish_name = serializers.CharField(max_length=32, read_only=True, source="publish.name")
    publish_address = serializers.CharField(max_length=32, read_only=True, source="publish.address")
    author_name = serializers.SerializerMethodField()

    def get_author_name(self, book_obj):
        author_list = list()
        for author in book_obj.authors.all():
            # 注意列表新增欄位,author.name而不是author
            author_list.append(author.name)
        return author_list
類BookSerializer

多個認證類

需要注意的是,如果需要返回什麼資料,請在最後一個認證類中返回,因為如果在前面返回,在原始碼的 self._authentication() 方法中會對返回值進行判斷,如果不為空,認證的過程就會中止,多個認證類的實現方式如下:

# 1.定義認證類
class UserAuth(object):
    # 認證邏輯
    def authenticate(self, request):
        user_token = request.GET.get('token')

        token_object = UserToken.objects.filter(token=user_token).first()
        if token_object:
            return token_object.user.username, token_object.token
        else:
            raise APIException("認證失敗")


class UserAuthTwo(object):
    def authenticate(self, request):
        raise APIException("就是這麼簡單!")


class BookView(ModelViewSet):
    # 2.指定認證類,固定寫法
    authentication_classes = [UserAuth, UserAuthTwo]
    # 獲取資料來源, 固定寫法
    queryset = models.Book.objects.all()
    # 序列化類, 固定寫法
    serializer_class = BookSerializer

全域性認證

如果希望所有的資料介面都需要認證怎麼辦?很簡單,如果認證類自己沒有 authentication_classes ,就會到 settings 中去找,通過這個機制,我們可以將認證類寫入到 settings 檔案中即可實現全域性認證:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'authenticator.utils.authentication.UserAuth',
        'authenticator.utils.authentication.UserAuthTwo',
    ),
}

~>.&l