认证校验
认证校验是十分重要的,如用户如果不登陆就不能访问某些接口。
@H_502_5@drf中认证的写法流程如下:
1.写一个类,继承BaseAuthentication,并且覆写其authenticate方法
2.当认证通过后应该返回两个值,并且第一个值会传递给request.user这个属性中,第二个值将会传递给request.auth这个属性中
3.如果认证失败,则抛出异常APIException或者AuthenticationFailed,它会自动捕获并返回
4.当前认证类设置是全局使用还是局部使用
准备工作
我们有一个登录功能,并且还有一个查询商品的接口,只有当用户登录后才能进行查询,否则就不可以。
模型表
两张表如下:
@H_502_5@from django.db import models class User(models.Model): # 用户 user_id = models.AutoField(primary_key=True) user_name = models.CharField(max_length=32) user_password = models.CharField(max_length=32) user_token = models.CharField(max_length=64,unique=True,null=True) # token,唯一 def __str__(self): return self.user_name class Meta: db_table = "" managed = True verbose_name = "User" verbose_name_plural = "Users" class Merchandise(models.Model): # 商品 merchandise_id = models.AutoField(primary_key=True) merchandise_name = models.CharField(max_length=32) merchandise_price = models.IntegerField() def __str__(self): return self.merchandise_name class Meta: db_table = "" managed = True verbose_name = "Merchandise" verbose_name_plural = "Merchandises"
用户表的数据如下:
商品表的数据如下:
也就是说,用户的@H_502_5@token自动如果为空,将会被认为没有登陆。
序列类
下面是序列类,我们只展示商品,用户列表将不会展示:
@H_502_5@from rest_framework.serializers import ModelSerializer from . import models class MerchandiseModelSerializer(ModelSerializer): class Meta: model = models.Merchandise fields = "__all__"
视图
@H_502_5@from uuid import uuid4 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from . import models from . import ser from . import authLogin # 导入认证的文件 class MerchandiseAPI(ModelViewSet): queryset = models.Merchandise.objects.all() serializer_class = ser.MerchandiseModelSerializer class Login(APIView): def post(self,request): # 代表用户登录 login_msg = { "user_name": request.data.get("user_name"),"user_password": request.data.get("user_password"),} user_obj = models.User.objects.filter(**login_msg).first() if user_obj: token = uuid4() # 生成随机字符串 user_obj.user_token = token user_obj.save() return Response(data="登录成功",headers={"token":token}) # 返回随机字符串 else: return Response(data="登录失败,用户名或密码错误")
url
@H_502_5@from django.contrib import admin from django.urls import path,re_path from rest_framework.routers import SimpleRouter from app01 import views router = SimpleRouter() router.register("merchandises",views.MerchandiseAPI) urlpatterns = [ path('admin/',admin.site.urls),path('login/',views.Login.as_view()),] urlpatterns.extend(router.urls)
基本使用
认证类
接下来我们要书写一个认证类:
@H_502_5@from rest_framework.authentication import BaseAuthentication # 继承的基类 from rest_framework.exceptions import AuthenticationFailed # 异常 from . import models from django.http import request class LoginVerify(BaseAuthentication): def authenticate(self,request): token = request.Meta.get("HTTP_TOKEN") # 如果在请求头中设置的是token的key名,获取时一定要全大写并加上HTTP if not token: raise AuthenticationFailed("请求失败,请求头中缺少token") else: user_obj = models.User.objects.filter(user_token=token).first() # 获取用户对象 if user_obj: return user_obj,user_obj.user_token # 返回用户本身和token。这样request.user里面就能拿到该用户了 else: raise AuthenticationFailed("token不存在,用户不存在,请不要伪造登录")
局部使用
只需要在商品接口中设置一个类属性,该接口便会进行认证。
@H_502_5@class MerchandiseAPI(ModelViewSet): authentication_classes = [authLogin.LoginVerify] # 使用认证 queryset = models.Merchandise.objects.all() serializer_class = ser.MerchandiseModelSerializer
全局使用
只需要在@H_502_5@settings.py中进行配置。
@H_502_5@REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.authLogin.LoginVerify",] }
如果想取消某个接口的认证,则在其中设置类属性@H_502_5@authentication_classes是一个空列表。
@H_502_5@class Login(APIView): authentication_classes = []
源码分析
流程分析
由于@H_502_5@modelViewSet继承自@H_502_5@APIView,所以我们直接看@H_502_5@as_view(),在下面这一句代码中,将会对@H_502_5@request进行二次封装。
@H_502_5@ def dispatch(self,request,*args,**kwargs): self.args = args self.kwargs = kwargs request = self.initialize_request(request,**kwargs) # 这里 self.request = request self.headers = self.default_response_headers # deprecate?
在二次封装中,实例化出了一个@H_502_5@Request对象并返回了,在实例化时,会调用@H_502_5@self.get_authenticators()方法,此时的@H_502_5@self是我们自定义的视图类,切记这一点。
@H_502_5@ def initialize_request(self,**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 )
下面是@H_502_5@get_authenticators()的代码,可以看见它会循环@H_502_5@self.authentication_classes这个可迭代对象,如果你没有传递这个可迭代对象,那么该对象是一个默认的设置。
@H_502_5@ def get_authenticators(self): return [auth() for auth in self.authentication_classes] # ( authLogin.LoginVerify调用,实例化 )
如果没有传递,将会找到@H_502_5@APIView中的默认设置:
@H_502_5@ class APIView(View): renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 默认的设置,默认的认证类,可以自己看一下
如果有进行传递,可以发现它是使用了一个括号,这就代表会调用,由于传入的是一个类,所以它会进行实例化。
所以我们可以认为@H_502_5@request.authenticators这个参数是一个@H_502_5@tuple,里面包含了认证类的实例化对象。
然后,@H_502_5@request就被二次包装完毕了。接下来执行 @H_502_5@self.initial(),现在的@H_502_5@self依然是我们自定义的视图类。
@H_502_5@ def dispatch(self,**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,**kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request,**kwargs)
@H_502_5@ def initial(self,**kwargs): self.format_kwarg = self.get_format_suffix(**kwargs) self.perform_authentication(request) # 只看这个,认证相关的 self.check_permissions(request) self.check_throttles(request)
到了@H_502_5@self.perform_authentication()时,它传递进了个@H_502_5@request,并且会去找@H_502_5@user这个属性抑或是被@H_502_5@property装饰的方法,所以我们需要到@H_502_5@Request这个类中去找,需要注意的是如果@H_502_5@user是一个方法,这代表会自动传递进@H_502_5@self,此时的@H_502_5@self则是我们经过二次封装的@H_502_5@request对象。
可以发现它是一个被装饰的方法。很显然我们没有@H_502_5@_user这个方法或属性,会执行@H_502_5@with语句,其实直接看@H_502_5@self._authenticate()即可。再次强调,此次的@H_502_5@self是二次封装的@H_502_5@request对象。
@H_502_5@ @property def user(self): if not hasattr(self,'_user'): with wrap_attributeerrors(): self._authenticate() return self._user
下面是整个代码的核心。
@H_502_5@ def _authenticate(self): for authenticator in self.authenticators: # 循环认证类对象 ( authLogin.LoginVerify的实例化 ) try: user_auth_tuple = authenticator.authenticate(self) # 这里会找authenticate方法并将request对象进行传递,我们的认证类继承了BaseAuthentication这个类,它会实现一个接口方法, 但会抛出异常。 except exceptions.APIException: # 如果没有实现接口方法,或在验证时抛出异常都会被这里捕获 self._not_authenticated() # 执行这里 self.user将会是匿名用户AnonymousUser,而self.auth则是None raise if user_auth_tuple is not None: # 如果返回的值不是空 self._authenticator = authenticator self.user,self.auth = user_auth_tuple # 分别赋值给self.user,以及self.auth中 return # 返回 self._not_authenticated() # 上面有认证对象就会return,没有还是设置匿名用户和None
最后总结
其实看了源码后,你可以发现我们的认证类可以不继承@H_502_5@BaseAuthentication,但是推荐继承会更规范,因为这个基类实现了抽象接口。
其次,它将返回的两个值分别赋值给了@H_502_5@request.user以及@H_502_5@request.auth。
如果你没有返回值,那么对应的,@H_502_5@request.user就是匿名用户,@H_502_5@request.auth就是@H_502_5@None。
如果你没有配置认证类,其实它会走默认的认证类。
老规矩,关于配置认证类时依旧是先用局部的,再用全局的,最后是用默认的,如果你的上面的源码确实有感觉了的话,应该能够看懂。