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等實現的認證。