15 认证+认证源码解析
认证的写法
# 作用:校验用户是否登录,如果登录了,继续往后走,如果没有登录,直接返回 # 登录功能---》 # 认证的使用 -第一步:写一个认证类,继承BaseAuthentication,重写authenticate 方法 -第二步:在 authenticate 方法中判断用户是否登录(取出用户携带的token,去判断) -第三步:如果认证通过,返回两个值,如果认证不通过抛异常 -# 在后续的request对象中,有这两个值,第一个给了request.user,第二个值给了request.auth -第四步:把写的认证类,配置在视图类中(跟请求和响应的配置一样),全局配置 # 写一个认证类,继承BaseAuthentication -鸭子类型:不显示的继承某个类,只要类中有共同的属性和方法,我们就属于同一类
要求所有的图书接口必须登录后才可以操作
#models.py
class User(models.Model): username=models.CharField(max_length=32) password=models.CharField(max_length=32) class UserToken(models.Model): token=models.CharField(max_length=32) user=models.OneToOneField(to="User",on_delete=models.CASCADE) class Book(models.Model): title=models.CharField(max_length=32) price=models.IntegerField() publish=models.CharField(max_length=32)
serializer.py
from rest_framework.serializers import ModelSerializer from app01.models import Book class BookSerializer(ModelSerializer): class Meta: model = Book fields="__all__"
views.py 局部配置
from django.shortcuts import render
from rest_framework.viewsets import ViewSet,ModelViewSet
from app01 import models
from rest_framework.response import Response
from rest_framework.decorators import action
import uuid
from app01.serializer import BookSerializer
from app01 import auth
class UserView(ViewSet):
authentication_classes = []
@action(methods=['POST'],detail=False)
def login(self,request):
username=request.data.get("username")
password=request.data.get("password")
user=models.User.objects.filter(username=username,password=password).first()
if user:
token=str(uuid.uuid4())
models.UserToken.objects.update_or_create(defaults={"token":token},user=user)
return Response({"code":100,"msg":"登录成功","token":token})
else:
return Response({"code":101,"msg":"用户名或密码不正确"})
# 局部使用,在视图类中使用 # 5个接口都需要登录以后才能访问 from .auth import LoginAuth class BookView(ModelViewSet): # authentication_classes = [LoginAuth,] # 登录认证 serializer_class = BookSerializer queryset = Book.objects.all()
auth.py
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from .models import UserToken class LoginAuth(BaseAuthentication): def authenticate(self, request): # token=request.query_params.get('token') # get请求的地址中取 # 请求头中取? # request.META http请求头中的一些数据,HTTP_大写 HTTP_TOKEN token = request.META.get('HTTP_TOKEN') # 去数据库查询该token串是否存在,如果存在,取出对应的用户,就是当前登录用户 user_token = UserToken.objects.all().filter(token=token).first() if user_token: # 这个用户登录了,放行 return user_token.user, token # 在后续的request对象中,有这两个值,第一个给了request.user,第二个值给了request.auth else: raise AuthenticationFailed('您没有登录')
urls.py
from django.urls import path, re_path,include from app01 import views from rest_framework.routers import SimpleRouter router=SimpleRouter() router.register('user',views.UserView,'user') router.register('book',views.BookView,'book') urlpatterns = [ # path('admin/', admin.site.urls), ] urlpatterns+=router.urls
局部配置和全局配置
# 局部使用,在视图类中使用 # 5个接口都需要登录以后才能访问 from .auth import LoginAuth class BookView(ModelViewSet): # authentication_classes = [LoginAuth,] # 登录认证 serializer_class = BookSerializer queryset = Book.objects.all() def list(self, request, *args, **kwargs): #重写list # 认证通过以后,request.user print(request.user) # 只要认证通过,进入到视图类中,这个就是当前登录用户(认证类返回的时候,必须给他) print(request.auth) return super().list(request, *args, **kwargs) #全局使用,配置在配置文件中 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ # 全局使用,所有接口都必须登录才能用 'app01.auth.LoginAuth' ] }
在请求头中携带token
认证的源码分析
#1 APIVIew----》dispatch方法---》self.initial(request, *args, **kwargs)---->有认证,权限,频率 #2 只读认证源码: self.perform_authentication(request) #3 self.perform_authentication(request)就一句话:request.user,需要去drf的Request对象中找user属性(方法) #4 Request类中的user方法,刚开始来,没有_user,走 self._authenticate() #5 核心,就是Request类的 _authenticate(self): def _authenticate(self): # 遍历拿到一个个认证器,进行认证 # self.authenticators配置的一堆认证类产生的认证类对象组成的 list #self.authenticators 你在视图类中配置的一个个的认证类:authentication_classes=[认证类1,认证类2],对象的列表 for authenticator in self.authenticators: try: # 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象) # 返回值:登陆的用户与认证的信息组成的 tuple # 该方法被try包裹,代表该方法会抛异常,抛异常就代表认证失败 user_auth_tuple = authenticator.authenticate(self) #注意这self是request对象 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()
认证组件的使用
# 写一个认证类 app_auth.py from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from app01.models import UserToken class MyAuthentication(BaseAuthentication): def authenticate(self, request): # 认证逻辑,如果认证通过,返回两个值 #如果认证失败,抛出AuthenticationFailed异常 token=request.GET.get('token') if token: user_token=UserToken.objects.filter(token=token).first() # 认证通过 if user_token: return user_token.user,token else: raise AuthenticationFailed('认证失败') else: raise AuthenticationFailed('请求地址中需要携带token') # 可以有多个认证,从左到右依次执行 # 全局使用,在setting.py中配置 REST_FRAMEWORK={ "DEFAULT_AUTHENTICATION_CLASSES":["app01.app_auth.MyAuthentication",] } # 局部使用,在视图类上写 authentication_classes=[MyAuthentication] # 局部禁用 authentication_classes=[]