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