在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字典中配一個鍵值便可。
在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
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 放限制的邏輯代碼。
在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中的查漏補缺。