深刻解析當下大熱的先後端分離組件django-rest_framework系列三

三劍客之認證、權限與頻率組件

認證組件

局部視圖認證

在app01.service.auth.py:python

複製代碼
複製代碼
class Authentication(BaseAuthentication): def authenticate(self,request): token=request._request.GET.get("token") token_obj=UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed("驗證失敗!") return (token_obj.user,token_obj)
複製代碼
複製代碼

在views.py:django

複製代碼
複製代碼
def get_random_str(user): import hashlib,time ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf8")) md5.update(bytes(ctime,encoding="utf8")) return md5.hexdigest() from app01.service.auth import * from django.http import JsonResponse class LoginViewSet(APIView):  authentication_classes = [Authentication,]  def post(self,request,*args,**kwargs): res={"code":1000,"msg":None} try: user=request._request.POST.get("user") pwd=request._request.POST.get("pwd") user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first() print(user,pwd,user_obj) if not user_obj: res["code"]=1001 res["msg"]="用戶名或者密碼錯誤" else: token=get_random_str(user) UserToken.objects.update_or_create(user=user_obj,defaults={"token":token}) res["token"]=token except Exception as e: res["code"]=1002 res["msg"]=e return JsonResponse(res,json_dumps_params={"ensure_ascii":False})
複製代碼
複製代碼

備註:一個知識點:update_or_create(參數1,參數2...,defaults={‘字段’:'對應的值'}),這個方法使用於:若是對象存在,則進行更新操做,不存在,則建立數據。使用時會按照前面的參數進行filter,結果爲True,則執行update  defaults中的值,不然,建立。json

使用方式: api

  使用認證組件時,自定製一個類,在自定製的類下,必須實現方法:authenticate(self,request),必須接收一個request參數,結果必須返回一個含有兩個值的元組,認證失敗,就拋一個異常rest_framework下的exceptions.APIException。而後在對應的視圖類中添加一個屬性:authentication_classes=[自定製類名]便可。app

 備註:from rest_framework.exceptions import AuthenticationFailed   也能夠拋出這個異常(這個異常是針對認證的異常)dom

使用這個異常時,須要在自定製的類中,定製一個方法:def authenticate_header(self,request):pass,每次這樣定製一個方法很麻煩,咱們能夠在自定義類的時候繼承一個類: BaseAuthenticationide

調用方式:from rest_framework.authentication import BaseAuthentication函數

 

爲何是這個格式,咱們看看這個組件內部的 實現吧!
無論是認證,權限仍是頻率都發生在分發以前:
post

複製代碼
class APIView(View): def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
複製代碼

全部的實現都發生在       self.initial(request, *args, **kwargs)        這行代碼中this

複製代碼
class APIView(View): 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)
複製代碼

咱們先看認證相關   self.perform_authentication

複製代碼
class APIView(View): 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
複製代碼

整個這個方法就執行了一句代碼:request.user   ,這個request是新的request,是Request()類的實例對象,  .user 確定在Request類中有一個user的屬性方法。

複製代碼
class Request(object): @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
複製代碼

這個user方法中,執行了一個 self._authenticate()方法,繼續看這個方法中執行了什麼:

複製代碼
class Request(object): def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ for authenticator in self.authenticators: try: user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple return self._not_authenticated()
複製代碼

這個方法中,循環一個東西:self.authenticators  ,這個self,是新的request,咱們看看這個self.authenticators是什麼。

複製代碼
class Request(object): """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: - request(HttpRequest). The original request instance. - parsers_classes(list/tuple). The parsers to use for parsing the request content. - authentication_classes(list/tuple). The authentications used to try authenticating the request's user. """ def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): self._request = request self.parsers = parsers or () self.authenticators = authenticators or ()
複製代碼

這個self.authenticators是實例化時初始化的一個屬性,這個值是實例化新request時傳入的參數。

複製代碼
class APIView(View): 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 )
複製代碼

實例化傳參時,這個參數來自於self.get_authenticators(),這裏的self就不是request,是誰調用它既是誰,看看吧:

複製代碼
class APIView(View): def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes]
複製代碼

這個方法,返回了一個列表推導式   [auth() for auth in self.authentication_classes] ,到了這裏,是否是很暈,這個self.authentication_classes又是啥,這不是咱們在視圖函數中定義的屬性嗎,值是一個列表,裏面放着咱們自定義認證的類

class APIView(View): authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

若是咱們不定義這個屬性,會走默認的值  api_settings.DEFAULT_AUTHENTICATION_CLASSES

  走了一圈了,咱們再走回去唄,首先,這個列表推導式中放着咱們自定義類的實例,回到這個方法中def _authenticate(self),當中傳的值self指的是新的request,這個咱們必須搞清楚,而後循環這個包含全部自定義類實例的列表,獲得一個個實例對象。

 

for authenticator in self.authenticators:
try: user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple return

 

而後執行每個實例對象的authenticate(self)方法,也就是咱們在自定義類中必須實現的方法,這就是緣由,由於源碼執行時,會找這個方法,認證成功,返回一個元組 ,認證失敗,捕捉一個異常,APIException。認證成功,這個元組會被self.user,self.auth接收值,因此咱們要在認證成功時返回含有兩個值的元組。這裏的self是咱們新的request,這樣咱們在視圖函數和模板中,只要在這個request的生命週期,咱們均可以經過request.user獲得咱們返回的第一個值,經過request.auth獲得咱們返回的第二個值。這就是認證的內部實現,很牛逼。

備註:因此咱們認證成功後返回值時,第一個值,最好時當前登陸人的名字,第二個值,按需設置,通常是token值(標識身份的隨機字符串)

 這種方式只是實現了對某一張表的認證,若是咱們有100張表,那這個代碼我就要寫100遍,複用性不好,因此須要在全局中定義。

全局視圖認證組件

settings.py配置以下:

1
2
3
REST_FRAMEWORK = {
     "DEFAULT_AUTHENTICATION_CLASSES" :[ "app01.service.auth.Authentication" ,]
}

爲何要這樣配置呢?咱們看看內部的實現吧:

  從哪開始呢?在局部中,咱們在視圖類中加一個屬性authentication_classes=[自定義類],那麼在內部,確定有一個默認的值:

class APIView(View): authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

默認值是api_settings中的一個屬性,看看這個默認值都實現了啥

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

首先,這個api_settings是APISettings類的一個實例化對象,傳了三個參數,那這個DEFAULTS是啥,看看:

複製代碼
#rest_framework中的settings.py  DEFAULTS = { # Base API policies 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ), 'DEFAULT_PARSER_CLASSES': ( 'rest_framework.parsers.JSONParser', 'rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser' ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.AllowAny', ), 'DEFAULT_THROTTLE_CLASSES': (), 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation', 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata', 'DEFAULT_VERSIONING_CLASS': None, # Generic view behavior 'DEFAULT_PAGINATION_CLASS': None, 'DEFAULT_FILTER_BACKENDS': (), # Schema 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', # Throttling 'DEFAULT_THROTTLE_RATES': { 'user': None, 'anon': None, }, 'NUM_PROXIES': None, # Pagination 'PAGE_SIZE': None, # Filtering 'SEARCH_PARAM': 'search', 'ORDERING_PARAM': 'ordering', # Versioning 'DEFAULT_VERSION': None, 'ALLOWED_VERSIONS': None, 'VERSION_PARAM': 'version', # Authentication 'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser', 'UNAUTHENTICATED_TOKEN': None, # View configuration 'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name', 'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description', # Exception handling 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', 'NON_FIELD_ERRORS_KEY': 'non_field_errors', # Testing 'TEST_REQUEST_RENDERER_CLASSES': ( 'rest_framework.renderers.MultiPartRenderer', 'rest_framework.renderers.JSONRenderer' ), 'TEST_REQUEST_DEFAULT_FORMAT': 'multipart', # Hyperlink settings 'URL_FORMAT_OVERRIDE': 'format', 'FORMAT_SUFFIX_KWARG': 'format', 'URL_FIELD_NAME': 'url', # Input and output formats 'DATE_FORMAT': ISO_8601, 'DATE_INPUT_FORMATS': (ISO_8601,), 'DATETIME_FORMAT': ISO_8601, 'DATETIME_INPUT_FORMATS': (ISO_8601,), 'TIME_FORMAT': ISO_8601, 'TIME_INPUT_FORMATS': (ISO_8601,), # Encoding 'UNICODE_JSON': True, 'COMPACT_JSON': True, 'STRICT_JSON': True, 'COERCE_DECIMAL_TO_STRING': True, 'UPLOADED_FILES_USE_URL': True, # Browseable API 'HTML_SELECT_CUTOFF': 1000, 'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", # Schemas 'SCHEMA_COERCE_PATH_PK': True, 'SCHEMA_COERCE_METHOD_NAMES': { 'retrieve': 'read', 'destroy': 'delete' }, }
複製代碼

很直觀的DEFAULTS是一個字典,包含多組鍵值,key是一個字符串,value是一個元組,咱們須要的數據:

'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), 這有啥用呢,api_settings是怎麼使用這個 參數的呢

複製代碼
class APISettings(object): """ A settings object, that allows API settings to be accessed as properties. For example: from rest_framework.settings import api_settings print(api_settings.DEFAULT_RENDERER_CLASSES) Any setting with string import paths will be automatically resolved and return the class, rather than the string literal. """ def __init__(self, user_settings=None, defaults=None, import_strings=None): if user_settings: self._user_settings = self.__check_user_settings(user_settings) self.defaults = defaults or DEFAULTS self.import_strings = import_strings or IMPORT_STRINGS self._cached_attrs = set()
複製代碼

好像也看不出什麼有用的信息,只是一些賦值操做,將DEFAULTS賦給了self.defaults,那咱們再回去看,

在視圖函數中,咱們不定義authentication_classes 就會執行默認的APIView下的authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES,經過這個api_settings對象調用值時,調不到會怎樣,咱們看看怎麼取值吧,首先會在__getattribute__方法中先找,找不到,取本身的屬性中找,找不到去本類中找,在找不到就去父類中找,再找不到就去__getattr__中找,最後報錯。按照這個邏輯,執行默認 值時最後會走到類中的__getattr__方法中

 

複製代碼
class APISettings(object): def __getattr__(self, attr): if attr not in self.defaults: raise AttributeError("Invalid API setting: '%s'" % attr) try: # Check if present in user settings val = self.user_settings[attr] except KeyError: # Fall back to defaults val = self.defaults[attr] # Coerce import strings into classes if attr in self.import_strings: val = perform_import(val, attr) # Cache the result  self._cached_attrs.add(attr) setattr(self, attr, val) return val
複製代碼

 

備註:調用__getattr__方法時,會將取的值,賦值給__getattr__中的參數attr,全部,此時的attr是DEFAULT_AUTHENTICATION_CLASSES,這個值在self.defaluts中,因此,代碼會執行到try中,val=self.user_settings[attr],這句代碼的意思是,val的值是self.user_settings這個東西中取attr,因此self.user_settings必定是個字典,咱們看看這個東西作l什麼

 

複製代碼
class APISettings(object): @property def user_settings(self): if not hasattr(self, '_user_settings'): self._user_settings = getattr(settings, 'REST_FRAMEWORK', {}) return self._user_settings
複製代碼

這個屬性方法,返回self._user_settings,這個值從哪裏來,看代碼邏輯,經過反射,來取settings中的REST_FRAMEWORK的值,取不到,返回一個空字典。那這個settings是哪的,是咱們項目的配置文件。

  綜合起來,意思就是若是在api_settings對象中找不到這個默認值,就從全局settings中找一個變量REST_FRAMEWORK,這個變量是個字典,從這個字典中找attr(DEFAULT_AUTHENTICATION_CLASSES)的值,找不到,返回一個空字典。而咱們的配置文件settings中並無這個變量,很明顯,咱們能夠在全局中配置這個變量,從而實現一個全局的認證。怎麼配?

  配置格式:REST_FRAMEWORK={「DEFAULT_AUTHENTICATION_CLASSES」:("認證代碼所在路徑","...")}

代碼繼續放下執行,執行到if attr in self.import_strings:       val = perform_import(val, attr)   這兩行就開始,根據取的值去經過字符串的形式去找路徑,最後獲得咱們配置認證的類,在經過setattr(self, attr, val)   實現,調用這個默認值,返回配置類的執行。

 

複製代碼
def perform_import(val, setting_name): """ If the given setting is a string import notation, then perform the necessary import or imports. """ if val is None: return None elif isinstance(val, six.string_types): return import_from_string(val, setting_name) elif isinstance(val, (list, tuple)): return [import_from_string(item, setting_name) for item in val] return val def import_from_string(val, setting_name): """ Attempt to import a class from a string representation. """ try: # Nod to tastypie's use of importlib. module_path, class_name = val.rsplit('.', 1) module = import_module(module_path) return getattr(module, class_name) except (ImportError, AttributeError) as e: msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) raise ImportError(msg)
複製代碼

 

備註:經過importlib模塊的import_module找py文件。

 

權限組件

局部視圖權限

在app01.service.permissions.py中:

複製代碼
from rest_framework.permissions import BasePermission class SVIPPermission(BasePermission): message="SVIP才能訪問!" def has_permission(self, request, view): if request.user.user_type==3: return True return False
複製代碼

在views.py:

複製代碼
from app01.service.permissions import * class BookViewSet(generics.ListCreateAPIView): permission_classes = [SVIPPermission,] queryset = Book.objects.all() serializer_class = BookSerializers
複製代碼

全局視圖權限

settings.py配置以下:

1
2
3
4
REST_FRAMEWORK = {
     "DEFAULT_AUTHENTICATION_CLASSES" :[ "app01.service.auth.Authentication" ,],
     "DEFAULT_PERMISSION_CLASSES" :[ "app01.service.permissions.SVIPPermission" ,]
}

權限組件同樣的邏輯,一樣的配置,要配置權限組件,就要在視圖類中定義一個變量permission_calsses = [自定義類]

一樣的在這個自定義類中,要定義一個固定的方法   def has_permission(self,request,view)  必須傳兩個參數,一個request,一個是

當前視圖類的對象。權限經過返回True,不經過返回False,咱們能夠定製錯誤信息,在自定義類中配置一個靜態屬性message="錯誤信息",也能夠繼承一個類BasePermission。跟認證同樣。

複製代碼
    def check_permissions(self, request): """ Check if the request should be permitted. Raises an appropriate exception if the request is not permitted. """ for permission in self.get_permissions(): if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None) )
複製代碼

邏輯很簡單,循環這個自定義類實例對象列表,從每個對象中找has_permission,if not  返回值:表示返回False,,就會拋一個異常,這個異常的信息,會先從對象的message屬性中找。

  一樣的,配置全局的權限,跟認證同樣,在settings文件中的REST_FRAMEWORK字典中配一個鍵值便可。

 

throttle(訪問頻率)組件

局部視圖throttle

在app01.service.throttles.py中:

複製代碼
複製代碼
from rest_framework.throttling import BaseThrottle VISIT_RECORD={} class VisitThrottle(BaseThrottle): def __init__(self): self.history=None def allow_request(self,request,view): remote_addr = request.META.get('REMOTE_ADDR') print(remote_addr) import time ctime=time.time() if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr]=[ctime,] return True history=VISIT_RECORD.get(remote_addr) self.history=history while history and history[-1]<ctime-60: history.pop() if len(history)<3: history.insert(0,ctime) return True else: return False def wait(self): import time ctime=time.time() return 60-(ctime-self.history[-1])
複製代碼
複製代碼

在views.py中:

複製代碼
from app01.service.throttles import * class BookViewSet(generics.ListCreateAPIView): throttle_classes = [VisitThrottle,] queryset = Book.objects.all() serializer_class = BookSerializers
複製代碼

全局視圖throttle

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",] }

一樣的,局部使用訪問頻率限制組件時,也要在視圖類中定義一個變量:throttle_classes = [自定義類],一樣在自定義類中也要定義一個固定的方法def allow_request(self,request,view),接收兩個參數,裏面放咱們頻率限制的邏輯代碼,返回True經過,返回False限制,同時要定義一個def wait(self):pass   放限制的邏輯代碼。

內置throttle類

在app01.service.throttles.py修改成:

複製代碼
class VisitThrottle(SimpleRateThrottle): scope="visit_rate" def get_cache_key(self, request, view): return self.get_ident(request)
複製代碼

settings.py設置:

複製代碼
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",], "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",], "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",], "DEFAULT_THROTTLE_RATES":{ "visit_rate":"5/m", } }
複製代碼

總結

   restframework的三大套件中爲咱們提供了多個粒度的控制。局部的管控和全局的校驗,均可以很靈活的控制。下一個系列中,將會帶來restframework中的查漏補缺。

相關文章
相關標籤/搜索