好久好久之前,Web站點只是做爲瀏覽服務器資源(數據)和其餘資源的工具,甚少有什麼用戶交互之類的煩人的事情須要處理,因此,Web站點的開發這根本不關心什麼人在何時訪問了什麼資源,不須要記錄任何數據,有客戶端請求,我即返回數據,簡單方便,每個http請求都是新的,響應以後當即斷開鏈接。django
而現在,互聯網的世界發生了翻天覆地的變化,用戶不只僅須要跟其餘用戶溝通交流,還須要跟服務器交互,不論是論壇類、商城類、社交類、門戶類仍是其餘各種Web站點,你們都很是重視用戶交互,只有跟用戶交互了,才能進一步留住用戶,只有留住了用戶,才能知道用戶需求,知道了用戶需求,纔會產生商機,有了用戶,就等於有了流量,纔可以騙到…額…是融到錢,有了資金企業才能繼續發展,可見,用戶交互是很是重要的,甚至能夠說是相當重要的一個基礎功能。瀏覽器
而談到用戶交互,則必需要談到咱們今天所要學習的知識點,認證、權限和頻率。首先咱們來看看認證。服務器
大部分人都知道cookie和session這兩種方式能夠保存用戶信息,這兩種方式不一樣的是cookie保存在客戶端瀏覽器中,而session保存在服務器中,他們各有優缺點,配合起來使用,可將重要的敏感的信息存儲在session中,而在cookie中能夠存儲不太敏感的數據。cookie
而token稱之爲令牌。cookie、session和token都有其應用場景,沒有誰好誰壞,不過開發數據接口類的Web應用,目前用token仍是比較多的。session
token認證的大體步驟是這樣的:app
定義 url dom
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 ,以下所示:ide
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值:post
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, 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
須要注意的是,若是須要返回什麼數據,請在最後一個認證類中返回,由於若是在前面返回,在源碼的 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.views.UserAuth', 'authenticator.views.UserAuthTwo', ), }
與認證組件幾乎差很少,只要判斷該用戶的級別,是否給予經過
定義權限類
from rest_framework.permissions import BasePermission class UserPermission(BasePermission): message = "您沒有權限訪問該數據" def has_permission(self, request, view): # 用戶級別是否超過普通用戶 if request.user.user_type > 1: return Truereturn False
一樣的邏輯,一樣的方式,只是執行權限的方法名與執行認證的方法名不同而已,名爲 has_permission ,而且須要將當前的視圖類傳遞給該方法。
視圖類中加入 permission_classes 變量:
class BookView(ModelViewSet): # 指定認證類,固定寫法 authentication_classes = [UserAuth] # 指定權限類,固定寫法 permission_classes = [UserPermission] # 獲取數據源, 固定寫法 queryset = models.Book.objects.all() # 序列化類, 固定寫法 serializer_class = BookSerializer
~>.<~