DRF:源码剖析 - 认证组件
引言
一、引入
在编程的世界中,我们认为,用户输入的所有数据都是不可靠的,不合法的,直接使用用户输入的数据是不安全的。
很久很久以前,Web站点只是作为浏览文档和其他资源的工具,甚少有什么用户交互之类的烦人的事情需要处理,所以,Web站点的开发这根本不关心什么人在什么时候访问了什么资源,不需要记录任何数据,有客户端请求,我即返回数据,简单方便,每一个http请求都是新的,响应之后立即断开连接。
而如今,不管是论坛类、商城类、社交类、门户类还是其他各类Web站点,大家都非常重视用户交互,只有跟用户交互了,才能进一步留住用户,只有留住了用户,才能知道用户需求,知道了用户需求,就是有商机,有了流量,才能够骗到…额…抱歉…是融到钱,有了资金企业才能继续发展,可见,用户交互是非常重要的,甚至可以说是至关重要的一个基础功能。
而谈到用户交互,必须要谈到我们知识点,认证、权限和频率
二、token令牌
我们知道cookie和session两种方式可以保存用户信息,这两种方式不同的是cookie保存在客户端浏览器中,而session保存在服务器中,他们各有优缺点,配合起来使用,可将重要的敏感的信息存储在session中,而在cookie中可以存储不太敏感的数据。
token称之为令牌。cookie、session和token都有其应用场景,没有谁好谁坏,不过我们开发数据接口类(视图中的类)的Web应用,目前用token还是比较多的。
token认证的大致步骤是这样的:
(1)用户登录,服务器端获取用户名密码,查询用户表,如果存在该用户且第一次登录(或者token过期),生成token,否则返回错误信息
(2)如果不是第一次登录,且token未过期,更新token值
因此,我们需要修改model.py,增加用户表、token表
models.py
认证组件
一、前提准备
使用认证组件,判断用户是否登录成功,认证登录身份,需要修改models.py,增加用户表和token表
1、settings中app注册 rest_framework
2、models.py
from django.db import models # Create your models here. # ------------------- 用于用户认证表 ------------------- class User(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=32) user_type_entry = ( (1, 'Delux'), (2, 'SVIP'), (3, 'VVIP') ) user_type = models.IntegerField(choices=user_type_entry) class UserToken(models.Model): user = models.OneToOneField('User', on_delete=models.CASCADE) token = models.CharField(max_length=128) # ------------------- 图书管理相关表 ------------- class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name class Publish(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) city = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.name class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) # 外键字段(一对多关系) publish = models.ForeignKey(to="Publish", related_name="book", related_query_name="book_query", on_delete=models.CASCADE) # 多对多字段 authors = models.ManyToManyField(to="Author")
3、创建序列化类
# 序列化类 from rest_framework import serializers class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ('title', 'price', 'publish', 'authors', 'author_list', 'publish_name', 'publish_city' ) # 返回给前端,显示的字段 extra_kwargs = { 'publish': {'write_only': True}, 'authors': {'write_only': True} } # 该字段只可写,不可读,不能够返回给前端 publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name') # 在field中,必须声明出该字段publish_name,否则会报错 publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city') author_list = serializers.SerializerMethodField() # 多对多的关系,需要SerializerMethodField,并且用"get_字段名"方法,取值 def get_author_list(self, book_obj): # 拿到queryset开始循环 [{}, {}, {}, {}] authors = list() for author in book_obj.authors.all(): authors.append(author.name) return authors
二、局部组件的认证
1、urls.py
from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('serializer/', include('serializer.urls')), ]
urlpatterns = [ # re_path(r'books/$', views.BookView.as_view()), # re_path(r'books/(?P<pk>\d+)/$', views.BookFilterView.as_view()), re_path(r'books/$', views.BookView.as_view({ 'get': 'list', 'post': 'create' })), re_path(r'books/(?P<pk>\d+)/$', views.BookView.as_view({ 'get': 'retrieve', 'put': 'update', 'delete': 'destroy' })), re_path(r'user/$',views.UserView.as_view()) ]
2、生成 token
用户登录的视图函数中,生成 token,存储在 token 表中
from rest_framework.views import APIView
from .utils import get_token
class UserView(APIView): def post(self, request): response = {} # 定义返回的消息 fields = {'username', 'password'} # 定义需要的用户信息 user_info = {} # 定义一个用户信息字典 if fields.issubset(set(request.data.keys())): # 判断是否为子集合 # username = request.data.get("username") # password = request.data.get("password") for key in fields: user_info[key] = request.data[key] if user_info: user_instance = User.objects.filter(**user_info).first() # 使用filter需要判断参数是否不为空,因为为空会报错 if user_instance is not None: # 用户存在, access_token = get_token.generate_token() # 在表中写入token值 UserToken.objects.update_or_create(user=user_instance, defaults={ 'token': access_token }) response['status_code'] = 200 response['status_message'] = '登陆成功' response['access_token'] = access_token response['user_role'] = user_instance.get_user_type_display() else: response['status_code'] = 201 response['status_message'] = '登录失败,用户名或密码错误' return JsonResponse(response)
utils / get_token.py
获取随机字符串
import uuid # 获取token随机字符串 def generate_token(): random_str = str(uuid.uuid4()).replace('-','') return random_str
3、创建认证类
utils / authentication_classes.py
方式一:(用该方法即可)
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import APIException from ..models import UserToken class UserAuth(BaseAuthentication): # 如果没有继承该类,就必须要写 authenticate_header(self,request)方法 # 所有的逻辑都在这里面 def authenticate(self, request): user_token = request.query_params.get('token') # query_params是固定的 try: token = UserToken.objects.get(token = user_token) # 携带的token与表中的token值一样 # 后面的权限会用到 return token.user,token.token # 这里返回,以后在request.user、request.auth 中接收,在权限认证的时候,直接使用request.user局能够获取到当前的登录用户
except Exception: raise APIException('用户没有认证')
方式二:
from rest_framework.exceptions import APIException class UserAuth():
def authenticate_header(self, request): pass
def authenticate(self,request): user_token = request.query_params.get("token") #获取的浏览器客户端的token try: token = UserToken.objects.get(token=user_token) #如果数据库中没有与客户端匹配的token就会 报错 return token.user.username,token.token except Exception: raise APIException("没有认证")
4、视图类中,添加认证类
class BookView(ModelViewSet): authentication_classes = [UserAuth] # 指定认证的类,变量名必须是 authentication_classes queryset = Book.objects.all() serializer_class = BookSerializer
三、多个认证类实现
我们还可以指定多个认证类,只是需要注意的是,如果需要返回什么数据,请在最后一个认证类中返回,因为如果在前面返回,在self._authentication()方法中会对返回值进行判断,如果不为空,认证的过程就会中止,多个认证类的实现方式如下:
class UserAuth2(object): def authenticate_header(self, request): pass def authenticate(self, request): raise APIException("认证失败") class UserAuth(object): def authenticate_header(self, request): pass def authenticate(self, request): user_post_token = request.query_params.get('token') token_object = UserToken.objects.filter(token=user_post_token).first() if token_object: return token_object.user.username, token_object.token else: raise APIException("认证失败") class BookView(ModelViewSet): authentication_classes = [UserAuth2, UserAuth] # 必须最后一个认证类有返回值
如果不希望每次都写那个无用的authenticate_header方法,我们可以这样:
from rest_framework.authentication import BaseAuthentication class UserAuth2(BaseAuthentication): def authenticate(self, request): raise APIException("认证失败") class UserAuth(BaseAuthentication): def authenticate(self, request): user_post_token = request.query_params.get('token') token_object = UserToken.objects.filter(token=user_post_token).first() if token_object: return token_object.user.user_name, token_object.token else: raise APIException("认证失败")
四、全局认证组件
如果希望所有的数据接口都需要认证怎么办?很简单,还是根据之前的经验,就是这句代码:
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES
如果认证类自己没有authentication_classes,就会到settings中去找,通过这个机制,我们可以将认证类写入到settings文件中即可实现全局认证:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'authenticator.utils.authentication.UserAuth', 'authenticator.utils.authentication.UserAuth2', ), }

浙公网安备 33010602011771号