rest framework之认证组件
一、认证源码流程
1、认证准备工作
在rest framework之APIView中提到过rest framework的视图不仅有CBV分发的特性,而且又对request进行了封装,其中封装的就有认证功能。在APIView类下的dispatch方法中:
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 #rest-framework重构request对象 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 #这里和CBV一样进行方法的分发 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
在dispatch方法中:
request = self.initialize_request(request, *args, **kwargs)
是重构request对象,initialize_request返回的就是一个request对象,封装了原request以及认证相关:
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, #传入原request parsers=self.get_parsers(), authenticators=self.get_authenticators(), #认证相关 negotiator=self.get_content_negotiator(), parser_context=parser_context )
在request对象中封装了authenticators这一个个写好的认证类的对象,可以自己配置的认证类,这样认证的准备工作已经完成了(其实就是request封装认证类对象的列表)。
2、认证过程
在dispatch方法中的代码中:
self.initial(request, *args, **kwargs) #request是已经重构的request
在initial方法中:
def initial(self, request, *args, **kwargs): ... # Ensure that the incoming request is permitted self.perform_authentication(request) #进行认证 self.check_permissions(request) self.check_throttles(request)
在perform_authentication方法中:
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封装对象
#方便寻找,导入 from rest_framework.request import Request
@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
在_authenticate方法中
def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ """ 三种情况: 1、正常的认证,元组不为空 2、认证失败,没有通过认证类中认证方法的检验,没有登陆 3、匿名用户,认证类中认证方法没有检验,返回的是None """ 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() #匿名用户执行
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER() #在settings中可以设置默认的 else: self.user = None #如果settings中没设置默认的就执行 if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
注意,认证类最好继承rest framework默认的基类,当然没有也是可行的
#基类 class BaseAuthentication(object): """ 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
其它模块导入的方式:
from rest_framework.authentication import BasicAuthentication
3、认证配置
那么一个视图如何使用认证类呢?
首先,在dispatch方法中知道request对认证进行了前期的准备工作,封装了一个个的认证类对象列表。这些认证类是从哪里读取的呢?
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes] #获取认证类对象列表
- 局部认证
如果在视图中设置了authentication_classes=[],就会优先使用我们自己配置的:
class IndexView(APIView): authentication_classes = [AuthToken] #认证类 def get(self,request): pass def post(self,request): pass
- 全局认证
如果没有进行视图的设置呢?此时就会去配置文件中寻找:
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS) def reload_api_settings(*args, **kwargs): setting = kwargs['setting'] if setting == 'REST_FRAMEWORK': #配置文件中的键值 api_settings.reload()
在settings文件中设置:
REST_FRAMEWORK = { # 全局使用认证类 "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.AuthToken', ],#注意里面是路径,将认证类写在utils文件夹的auth.py中 }
这样,既可以做全局认证,又可以做局部认证,当全局认证中某一个视图不做认证的话(比如登陆),只需要在其视图中将authentication_classes 列表置空即可。
- 匿名用户配置
在认证类中的认证方法authenticate中执行的有三种结果,如果返回的结果是None,就会返回AnonymousUser,在settings.py中也是可以设置它的返回值的
REST_FRAMEWORK = { # "UNAUTHENTICATED_USER":lambda :"匿名用户" "UNAUTHENTICATED_USER":None, # 匿名,request.user = None "UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None }
总结:
二、实现
1、认证类
from rest_framework.authentication import BaseAuthentication import time import hashlib from crm.models import * from rest_framework.exceptions import AuthenticationFailed class AuthToken(BaseAuthentication): """ 认证类 """ def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). 在请求头中获取token进行验证,如果前端的请求头是Authorization,django请求头必须以大写HTTP开头, 中间以_分隔的大写键值 """ token = request._request.META.get("HTTP_AUTHORIZATION") token_obj = UserToken.objects.filter(token=token).first() if not token_obj: raise AuthenticationFailed("用户未登陆!") return (token_obj.user,token_obj) 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 def get_md5(username): """ 生成对应用户的token :param username: 用户名 :return: """ m=hashlib.md5(bytes(username,encoding="utf-8")) #加盐 ctime = str(time.time()) m.update(bytes(ctime,encoding="utf-8")) return m.hexdigest()
2、使用
class UserModelView(viewsets.ModelViewSet): authentication_classes = [AuthToken,] #配置认证类 #认证成功后可以拿到元组参数 request.user,request.auth queryset = models.UserInfo.objects.all() serializer_class = AuthorModelSerlizer = UserModelSerlizer
如果认证失败的话就会返回认证类中抛出的异常:
{ ”ddetail“:"用户未登陆" }
配置认证类也可以在settings.py中进行全局配置:
REST_FRAMEWORK = { # 全局使用认证类 "DEFAULT_AUTHENTICATION_CLASSES":['crm.utils.auth.AuthToken', ], }
3、前端提交token
除了登陆页面外的其它页面都应该提供token,这里在提交数据时将token附带在请求头中提交
getUsers() { //将token设置在请求头中提交 this.$http.defaults.headers.common['Authorization'] = localStorage.getItem("token"); this.$store.dispatch('user/getAllUserList',this)//this是Vue实例 }