Title(EN): Django REST Framework Learning Notes (11): Authentication
Author: dog2
基本信息
源码 rest_framework.authentication
官方文档 API Guide - Authentication
本文demo代码Github
源码分析
入口
从rest_framework.views.APIView
的dispath(self, request, *args, **kwargs)
下手 ,dispath
方法内 self.initial(request, *args, **kwargs)
进入三大认证
认证组件 self.perform_authentication(request)
校验用户:游客、合法用户、非法用户
游客:代表校验通过,直接进入下一步校验(权限校验)
合法用户:代表校验通过,将用户存储在request.user
中,再进入下一步校验(权限校验)
非法用户:代表校验失败,抛出异常,返回403权限异常结果
权限组件 self.check_permissions(request)
校验用户权限:必须登录、所有用户、登录之后读写,游客只读、自定义用户角色
认证通过:可以进入下一步校验(频率认证)
认证失败:抛出异常,返回403权限异常结果
频率组件 self.check_throttles(request)
限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)
没有达到限次:正常访问接口
达到限次:限制时间内不能访问,限制时间达到后,可以重新访问
本文介绍认证组件。
rest_framework.views.APIView.initial()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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) neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
1 2 3 4 5 6 7 8 9 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
rest_framework.request.Request.user
1 2 3 4 5 6 7 8 9 10 @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
rest_framework.request.Request._authenticate()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def _authenticate (self) : 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()
自定义认证类
方法
从源码的settings
文件可以看出,认证类需要继承BasicAuthentication
(在authentication.py文件)
1 2 3 4 DEFAULT_AUTHENTICATION_CLASSES': [ ' rest_framework.authentication.SessionAuthentication', #会重新开启CSRF认证 ' rest_framework.authentication.BasicAuthentication' ]
具体流程如下:
创建继承BaseAuthentication
的认证类
重写authenticate
方法
实现体根据认证规则 确定游客、非法用户、合法用户 (根据自己的认证规则)
没有认证信息返回None
(游客)
有认证信息认证失败抛异常(非法用户)
有认证信息认证成功返回用户与认证信息元组(合法用户)
进行全局或局部配置
示例代码
models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 from django.db import modelsfrom django.contrib.auth.models import AbstractUserclass User (AbstractUser) : mobile = models.CharField(max_length=11 ,unique=True ) class Meta : db_table = 'user' verbose_name = '用户表' verbose_name_plural = verbose_name def __str__ (self) : return self.username
utils.authentications.py
Source Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 from rest_framework.authentication import BaseAuthenticationfrom rest_framework.exceptions import AuthenticationFailed from app01 import modelsclass MyAuthentication (BaseAuthentication) : """ 同前台请求头拿认证信息auth(获取认证的字段要与前台约定) 没有auth是游客,返回None 有auth进行校验 失败是非法用户,抛出异常 成功是合法用户,返回 (用户, 认证信息) """ def authenticate (self, request) : auth = request.META.get('HTTP_AUTHORIZATION' ,None ) if auth is None : return None auth_list = auth.split() if not (len(auth_list) == 2 and auth_list[0 ].lower() == 'auth' ): raise AuthenticationFailed('认证信息有误,非法用户' ) if auth_list[1 ] != 'abc.123.xyz' : raise AuthenticationFailed('信息错误,非法用户' ) user = models.User.objects.filter(username='admin' ).first() if not user: raise AuthenticationFailed('用户数据有误,非法用户' ) return (user,None )
settings.py
在settings
文件中配置自定义认证组件
1 2 3 4 5 6 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES' : [ 'utils.authentications.MyAuthentication' , ], }
views.py
1 2 3 4 5 6 7 8 9 10 from rest_framework.views import APIViewfrom utils.response import APIResponseclass TestAPIView (APIView) : def get (self, request, *args, **kwargs) : print(request.user) return APIResponse(0 , 'test get ok' )
urls.py
1 2 3 4 5 6 from django.urls import pathfrom . import viewsurlpatterns = [ path('test/' , views.TestAPIView.as_view()), ]
Postman测试
使用Postman的get请求,在自定义认证组件获取用户,在views视图通过request.user
能打印出来
Token认证
JWT
RESTful API里使用最普遍的就是基于json的token认证了,即Json Web Token(JWT)。JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。
优点
服务器不要存储token,token交给每一个客户端自己存储,服务器压力小
服务器存储的是 签发和校验token 两段算法,签发认证的效率高
算法完成各集群服务器同步成本低,路由项目完成集群部署(适应高并发)
JWT格式
jwt token采用三段式:头部.载荷.签名
每一部分都是一个json字典加密形参的字符串
头部和载荷采用的是base64可逆加密(前台后台都可以解密)
签名采用hash256不可逆加密(后台校验采用碰撞校验)
各部分字典的内容:
头部:基础信息 - 公司信息、项目组信息、可逆加密采用的算法
载荷:有用但非私密的信息 - 用户可公开信息、过期时间
签名:头部+载荷+秘钥 不可逆加密后的结果
注:服务器jwt签名加密秘钥一定不能泄露
签发与校验
签发token:固定的头部信息加密。当前的登陆用户与过期时间加密。头部+载荷+秘钥生成不可逆加密
校验token:头部可校验也可以不校验,载荷校验出用户与过期时间,头部+载荷+秘钥完成碰撞检测校验token是否被篡改
DRF自带的JWT认证
DRF的源码里是实现了token认证模块了的,不过存在一些不足 ,因此一般不推荐使用。
一般使用的是DRF的两个jwt插件:
jango-rest-framework-jwt
: 曾经很常用,但后来作者已不再维护,因此也不推荐使用
django-rest-framework-simplejwt
: DRF官方文档 推荐使用的插件
DRF插件 django-rest-framework-jwt
示例代码如下
settings.py
1 2 3 4 5 6 7 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES' : [ 'utils.authentications.MyAuthentication' , 'rest_framework_jwt.authentication.JSONWebTokenAuthentication' , ], }
models.py
代码同上
views.py
1 2 3 4 5 6 7 8 9 10 11 from rest_framework.views import APIViewfrom utils.response import APIResponsefrom rest_framework.permissions import IsAuthenticatedfrom rest_framework_jwt.authentication import JSONWebTokenAuthenticationclass UserDetail (APIView) : authentication_classes = [JSONWebTokenAuthentication] permission_classes = [IsAuthenticated] def get (self, request, *args, **kwargs) : return APIResponse(results={'username' : request.user.username})
url.py
1 2 3 4 5 6 7 8 from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_tokenfrom . import viewsurlpatterns = [ path('jwt/' , obtain_jwt_token), path('user/detail/' , views.UserDetail.as_view()),
DRF插件 django-rest-framework-simplejwt
示例代码如下
settings.py
1 2 3 4 5 6 7 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES' : [ 'utils.authentications.MyAuthentication' , 'rest_framework_simplejwt.authentication.JWTAuthentication' , ], }
此外,还有simplejwt的一些细节配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME' : timedelta(minutes=5 ), 'REFRESH_TOKEN_LIFETIME' : timedelta(days=1 ), 'ROTATE_REFRESH_TOKENS' : False , 'BLACKLIST_AFTER_ROTATION' : True , 'ALGORITHM' : 'HS256' , 'SIGNING_KEY' : SECRET_KEY, 'VERIFYING_KEY' : None , 'AUTH_HEADER_TYPES' : ('Bearer' ,), 'USER_ID_FIELD' : 'id' , 'USER_ID_CLAIM' : 'user_id' , 'AUTH_TOKEN_CLASSES' : ('rest_framework_simplejwt.tokens.AccessToken' ,), 'TOKEN_TYPE_CLAIM' : 'token_type' , 'JTI_CLAIM' : 'jti' , 'SLIDING_TOKEN_REFRESH_EXP_CLAIM' : 'refresh_exp' , 'SLIDING_TOKEN_LIFETIME' : timedelta(minutes=5 ), 'SLIDING_TOKEN_REFRESH_LIFETIME' : timedelta(days=1 ), }
models.py
代码同上
views.py
1 2 3 4 5 6 7 8 9 10 11 from rest_framework.views import APIViewfrom utils.response import APIResponsefrom rest_framework.permissions import IsAuthenticatedfrom rest_framework_simplejwt.authentication import JWTAuthenticationclass UserDetail2 (APIView) : authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated] def get (self, request, *args, **kwargs) : return APIResponse(results={'username' : request.user.username})
url.py
1 2 3 4 5 6 7 8 9 from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_tokenfrom . import viewsurlpatterns = [ path('simple_jwt/' , TokenObtainPairView.as_view(), name='token_obtain_pair' ), path('simple_jwt/refresh/' , TokenRefreshView.as_view(), name='token_refresh' ), path('user/detail2/' , views.UserDetail2.as_view()),
参考链接 & 扩展阅读
【DRF】用户注册登录及JWT JSON Web Token 入门教程