drf認證組件
用戶信息表 from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): mobile = models.CharField(max_length=11,unique=True) class Meta: db_table = 'user' verbose_name = '用戶表' verbose_name_plural = verbose_name def __str__(self): return self.username
源碼分析
1.從APIView的dispath(self, request, *args, **kwargs)下手 2.dispath方法內 self.initial(request, *args, **kwargs) 進入三大認證 認證組件:校驗用戶 —游客、合法用戶、非法用戶 # 游客:代表校驗通過,直接進入下一步校驗(權限校驗) # 合法用戶:代表校驗通過,將用戶存儲在request.user中,再進入下一步校驗(權限校驗) # 非法用戶:代表校驗失敗,拋出異常,返回403權限異常結果 self.perform_authentication(request) #認證組件 權限組件:校驗用戶權限 - 必須登錄、所有用戶、登錄之后讀寫,游客只讀、自定義用戶角色 # 認證通過:可以進入下一步校驗(頻率認證) # 認證失敗:拋出異常,返回403權限異常結果 self.check_permissions(request) #權限組件 頻率組件:限制視圖接口被訪問的頻率次數 - 限制的條件(IP、id、唯一鍵)、頻率周期時間(s、m、h)、頻率的次數(3/s) # 沒有達到限次:正常訪問接口 # 達到限次:限制時間內不能訪問,限制時間達到后,可以重新訪問 self.check_throttles(request) #頻率組件
分兩方面:a,b
(a):在initial方法上面——這個是源碼settings文件配置認證組件的地方
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
def get_authenticators(self): return [auth() for auth in self.authentication_classes]
源碼settings.py文件中APISettings類
DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', #session認證 'rest_framework.authentication.BasicAuthentication' #基礎認證 ]
(b): self.inital(self,request,*args,**kwargs) 包含認證,權限,頻率三大組件
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) #認證 self.check_permissions(request) #權限 self.check_throttles(request) #頻率
(b---1): self.perform_authentication(request) 認證
def perform_authentication(self, request): """ Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication will instead be performed lazily, the first time either `request.user` or `request.auth` is accessed. """ request.user
(b---2):Request類的 方法屬性 user 的get方法
@property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """ if not hasattr(self, '_user'): with wrap_attributeerrors(): self._authenticate() return self._user
(b---3):self._authenticate()
認證的細則: # 做認證 def _authenticate(self): #這里的self就是request # 遍歷拿到一個個認證器,進行認證 # self.authenticators配置的一堆認證類產生的認證類對象組成的 list
# 即:[auth() for auth in self.authentication_classes] for authenticator in self.authenticators: try: # 認證器(對象)調用認證方法authenticate(認證類對象self, request請求對象) # 返回值:登陸的用戶與認證的信息組成的 tuple # 該方法被try包裹,代表該方法會拋異常,拋異常就代表認證失敗 user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise # 返回值的處理 if user_auth_tuple is not None: self._authenticator = authenticator # 如何有返回值,就將 登陸用戶 與 登陸認證 分別保存到 request.user、request.auth self.user, self.auth = user_auth_tuple return # 如果返回值user_auth_tuple為空,代表認證通過,但是沒有 登陸用戶 與 登陸認證信息,代表游客 self._not_authenticated()
自定義認證類
從源碼的settings文件可以看出,認證類需要繼承BasicAuthentication(在authentication.py文件)
DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', #會重新開啟CSRF認證 'rest_framework.authentication.BasicAuthentication' ]
1) 創建繼承BaseAuthentication的認證類 2) 重寫authenticate方法 3) 實現體根據認證規則 確定游客、非法用戶、合法用戶 (根據自己的認證規則) 4) 進行全局或局部配置 認證規則 i.沒有認證信息返回None(游客) ii.有認證信息認證失敗拋異常(非法用戶) iii.有認證信息認證成功返回用戶與認證信息元組(合法用戶)
utils.authentications.py
# 1)繼承BaseAuthentication類 # 2)重寫authenticate(self, request)方法,自定義認證規則 # 3)自定義認證規則基於的條件: # 沒有認證信息返回None(游客) # 有認證信息認證失敗拋異常(非法用戶) # 有認證信息認證成功返回用戶與認證信息元組(合法用戶) # 4)完全視圖類的全局(settings文件中)或局部(確切的視圖類)配置 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed #異常接收 from app01 import models #繼承BaseAuthentication class MyAuthentication(BaseAuthentication): """ 同前台請求頭拿認證信息auth(獲取認證的字段要與前台約定) 沒有auth是游客,返回None 有auth進行校驗 失敗是非法用戶,拋出異常 成功是合法用戶,返回 (用戶, 認證信息) """ def authenticate(self, request): #重寫authenticate方法 #前台在請求頭攜帶認證信息, #且默認規范用 Authorization 字段攜帶認證信息, #后台固定在請求對象的META字段中 HTTP_AUTHORIZATION 獲取 #認證信息auth auth = request.META.get('HTTP_AUTHORIZATION',None)#處理游客 if auth is None: return None #設置認證字段小規則(兩段式):"auth 認證字符串" 在BasicAuthentication類中有規則范式 auth_list = auth.split() #校驗是否還是非法用戶,不是兩段,第一段不是auth就是非法用戶 if not (len(auth_list) == 2 and auth_list[0].lower() == 'auth'): raise AuthenticationFailed('認證信息有誤,非法用戶') #拋異常 #校驗認證信息第二段從auth_list[1]中解析出來 # 注:假設一種情況,信息為abc.123.xyz,就可以解析出admin用戶;實際開發,該邏輯一定是校驗用戶的正常邏輯 if auth_list[1] != 'abc.123.xyz': #校驗失敗 raise AuthenticationFailed('信息錯誤,非法用戶') #最后再去數據庫校驗是否有此用戶 user = models.User.objects.filter(username='admin').first() if not user: raise AuthenticationFailed('用戶數據有誤,非法用戶') return (user,None)
在settings文件中配置自定義認證組件
REST_FRAMEWORK = { # 認證類配置 'DEFAULT_AUTHENTICATION_CLASSES': [ 'utils.authentications.MyAuthentication', ], }
視圖層:views.py
class TestAPIView(APIView): def get(self, request, *args, **kwargs): # 如果通過了認證組件,request.user就一定有值 # 游客:AnonymousUser # 用戶:User表中的具體用戶對象 print(request.user) return APIResponse(0, 'test get ok')
路由層:urls.py
urlpatterns = [ url(r'^test/$', views.TestAPIView.as_view()), ]
注意使用Postman請求:
使用Postman的get請求,在自定義認證組件獲取用戶,在views視圖通過request.user能打印出來
注意配置:
認證組件一般都是自定義的,不會使用原始的,自定義好需要在settings配置就是全局使用
drf權限組件
源碼分析
1. 從APIView的dispatch開始
2. dispatch 方法內 self.initial(request,*args,**kwargs)
3. self.initial(request,*args,**kwargs)
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) self.check_permissions(request) #權限組件 self.check_throttles(request)
4. self.check_permissions(request) 權限細節
def check_permissions(self, request): # 遍歷權限對象列表得到一個個權限對象(權限器),進行權限認證 for permission in self.get_permissions(): # 權限類一定有一個has_permission權限方法,用來做權限認證的 # 參數:權限對象self、請求對象request、視圖類對象 # 返回值:有權限返回True,無權限返回False if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None) )
5.self.get_permissions() 獲取權限類對象列表
def get_permissions(self): """ Instantiates and returns the list of permissions that this view requires. """ return [permission() for permission in self.permission_classes]
6.self.permission_classes 獲取權限settings源碼配置
DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', #所有權限 ]
在源碼permissions.py中的系統自帶權限類,這里舉例四個
1)AllowAny: 默認是這個權限 認證規則全部返還True:return True 游客與登陸用戶都有所有權限 2) IsAuthenticated: 常用這個 認證規則必須有登陸的合法用戶:return bool(request.user and request.user.is_authenticated)
有登錄用戶名並且認證成功 游客沒有任何權限,登陸用戶才有權限 3) IsAdminUser: 認證規則必須是后台管理用戶:return bool(request.user and request.user.is_staff) 游客沒有任何權限,登陸用戶才有權限 4) IsAuthenticatedOrReadOnly 認證規則必須是只讀請求或是合法用戶: return bool( request.method in SAFE_METHODS or request.user and request.user.is_authenticated ) 游客只讀,合法用戶無限制
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
權限組使用:全局使用,局部使用
全局使用:默認全局配置的權限類是AllowAny,在settings文件中配置
REST_FRAMEWORK = { # 權限類配置 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', #默認所有權限 ], }
局部使用:views.py
#只有登錄后才能訪問
from rest_framework.permissions import IsAuthenticated class TestAuthenticatedAPIView(APIView): permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): return APIResponse(0, 'test 登錄才能訪問的接口 ok')
自定義權限類
1) 創建繼承BasePermission的權限類 2) 實現has_permission方法 3) 實現體根據權限規則 確定有無權限 4) 進行全局或局部配置 認證規則 i.滿足設置的用戶條件,代表有權限,返回True ii.不滿足設置的用戶條件,代表有權限,返回False
utils.permissions.py
from rest_framework.permissions import BasePermission from django.contrib.auth.models import Group class MyPermission(BasePermission): def has_permission(self, request, view): # 只讀接口判斷 r1 = request.method in ('GET', 'HEAD', 'OPTIONS') # group為有權限的分組 group = Group.objects.filter(name='管理員').first() # groups為當前用戶所屬的所有分組 groups = request.user.groups.all() r2 = group and groups r3 = group in groups # 讀接口大家都有權限,寫接口必須為指定分組下的登陸用戶 return r1 or (r2 and r3)
視圖層:views.py 局部使用自定義的Mypermission來做權限認證
# 游客只讀,登錄用戶只讀,只有登錄用戶屬於 管理員 分組,才可以增刪改 from utils.permissions import MyPermission class TestAdminOrReadOnlyAPIView(APIView): permission_classes = [MyPermission] # 所有用戶都可以訪問 def get(self, request, *args, **kwargs): return APIResponse(0, '自定義讀 OK') # 必須是 自定義“管理員”分組 下的用戶 def post(self, request, *args, **kwargs): return APIResponse(0, '自定義寫 OK')
路由層:urls.py
urlpatterns = [ url(r'^test3/$', views.TestAdminOrReadOnlyAPIView.as_view()), ]
Postman使用:
get請求:加不加Authorization,都可以讀
post請求:不加Authorization,就不能進行寫操作,必須要寫Authorization
