rest_framework框架之认证功能的使用和源码实现流程分析
rest_framework框架之认证的使用和源码实现流程分析
一、认证功能的源码流程
- 创建视图函数
Note
创建视图函数后,前端发起请求,url分配路由,执行视图类,视图类中执行对应方法必须经过dispatch()即调度方法
from rest_framework.views import APIView
from django.shortcuts import HttpResponse
import json
class DogView(APIView):
def get(self, request, *args, **kwargs):
result = {
'code': '10000',
'msg': '数据创建成功'
}
return HttpResponse(json.dumps(result))
def post(self, request, *args, **kwargs):
return HttpResponse('创建一条订单')
def put(self, request, *args, **kwargs):
return HttpResponse('更新一条订单')
def delete(self, request, *args, **kwargs):
return HttpResponse('删除一条订单')
- 运行dispatch方法
Note
如果自己定义了dispatch方法,则程序运行自定义方法,如果没有,程序运行源码中的dispatch方法。从dispatch方法中可以找到原生request在作为参数传递后被initialize_request()函数进行了加工,通过加工的request获得的值包括原生的request和BaseAuthentication实例化对象,所以我们需要找到initialize_request()。
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
request = self.initialize_request(request, *args, **kwargs)
'''
对原生的request进行加工,获得到的request已经不是原来的request,还包括了其他的参数,
可以通过新的request获取到内部包含的参数
加工后的request : Restquest(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
))
'''
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# 把加工后的request当作参数传递给了initial()函数
# 需要把在这里查找initial()函数
# Get the appropriate handler method
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
- 查看initialize_request()函数
Note
在initialize_request()函数中返回了authenticators, 通过观察可以看出,authenticators的值来自于另外一个函数get_authenticators()。
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(),
# authenticators获取到的是实例化后的认证类对象列表,即[Foo(), Bar()]
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
- 找到函数self.get_authenticators()
Note
这个函数中实质上是把一个认证类列表实例化为对象列表进行返回,这里就可以得出在上一个函数中的authenticators是一个实例化对象列表。需要继续往源头找,查找authentication_classes
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
# 例如self.authentication_classes = [foo, bar]
return [auth() for auth in self.authentication_classes]
# 列表生成式,auth获取到的是列表中的类,auth()是把获取到的类对象进行实例化操作
- 查找authentication_classes类
Note
在自己编写的代码中如果定义了认证类,则执行自定义认证类,如果没有定义authentication_classes类,程序会从继承的类中去查找,视图类继承自APIView,所以在APIView中找到类authentication_classes。
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
# 继承自APIView中的api_settings.DEFAULT_AUTHENTICATION_CLASSES类
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
Summary
从上述的逻辑可以看出最终要执行的是AUTHENTICATION_CLASSES,所有的程序中都是如果有自定义程序会覆盖掉框架封装好的,没有自定义,程序才会执行封装好的代码。AUTHENTICATION_CLASSES类是这个逻辑中最重要的一环。
上边的代码查找到了最基本的Authentication_classes,并且得到加工后的request包含两部分内容:原生的request、Authentication_classes实例化后得到的对象列表,此时需要继续执行dispatch(),执行到try语句时,加工后的request作为参数传递给initial()函数,并执行该函数,此时需要到request.py中查找initial()函数。
self.request = request
self.headers = self.default_response_headers # deprecate? try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
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
查找initial()方法,在该方法中找到perform_authentication(request)方法,继续查找perform_authentication(request)方法
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) # Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
perform_authentication方法中调用了request.py中的Request类的user()方法
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类中查找到request被传递进行,原生的参数在调用的时候格式为:request._request, 加工后的直接是request.属性
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs:
- request(HttpRequest). The original request instance.
- parsers_classes(list/tuple). The parsers to use for parsing the
request content.
- authentication_classes(list/tuple). The authentications used to try
authenticating the request's user.
""" def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
assert isinstance(request, HttpRequest), (
'The `request` argument must be an instance of '
'`django.http.HttpRequest`, not `{}.{}`.'
.format(request.__class__.__module__, request.__class__.__name__)
) self._request = request
# 加工后的request被作为参数传递,那么传递后相对于本类即为原生的request。
self.parsers = parsers or ()
self.authenticators = authenticators or ()
self.negotiator = negotiator or self._default_negotiator()
self.parser_context = parser_context
self._data = Empty
self._files = Empty
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
如果进行认证,必须通过user,此时需要查找user程序是否存在,在Request类中找到了user方法,user()方法执行了_authenticate(),查找_authenticate()
@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()
# 执行_authenticate()
return self._user
查找_authenticate(),在_authenticate()方法中查找到Authenticator_classes生成的实例化列表类对象,循环的对象具有authenticate()属性/方法,可以直接调用,并通过条件语句判断,如果登陆返回元组,如果没有登陆返回错误提示。此时基本的逻辑已经梳理完成。
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
# 如果有返回值,继续执行
except exceptions.APIException:
raise self._not_authenticated()
# 没有返回值则抛出_not_authenticated()异常 if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
# authenticate()方法返回的元组存在,那么把元组的内容分别赋值给user, auth
return self._not_authenticated()
查找异常处理方法_not_authenticated(),当前边的方法判断后没有收到元组数据,程序抛出了异常,这个异常执行_not_authenticated()方法,方法中直接调用框架自定义的api_settings.UNAUTHENTICATED_USER()类,如果存在user为AnonymousUser(匿名用户), auth为None,如果不存在,user和auth都直接赋值为None。
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()
else:
self.user = None if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
二、自定义认证类
通过上述逻辑的整体分析,我们可以编写一个自定义的认证类供视图函数来调用,自定义的认证类必须具有两个方法:authenticate()和authenticate_header()方法,authenticate()必须返回一个元组,元组第一个元素为user,第二个元素为token对象
# 为测试程序临时创建的数据
ORDER_DICT = {
1: {
'name': 'dog',
'age': 2,
'gender': 'male'
},
2: {
'name': 'cat',
'age': 3,
'gender': 'female'
}
}
# 自定义Authentication_classes
from rest_framework import exceptions
from api.models import UserToken
class MyAuthentication(object):
def authenticate(self, request, *args, **kwargs):
token = request._request.GET.get('token')
token_obj = UserToken.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed("用户尚未登陆")
return (token_obj.user, token_obj)
def authenticate_header(self, request):
pass
# 生成随机字符串token
def md5(username):
# 以用户post传过来的username和时间来作为参数,随机生成token,
# 需要注意的是在创建models是username字段必须是唯一的。
import time
import hashlib
ctime = str(time.time)
m = md5.hashlib(ytes(username, encodig='utf-8'))
# 生成的随机字符串编码为utf-8
m.update(bytes(ctime, encoding='utf-8'))
return m.hexdigest()
# 创建认证视图
from rest_framework.views import APIView
from api.models import UserInfo
from django.http import JsonResponse
class AuthView(APIView):
def post(self, request, *args, **kwargs):
# 虽然是验证信息,也是需要用户提交过来信息的,所以这里是post方法
result = {
'code': '1000',
'msg': None
}
try:
username = request._request.GET.get('username')
password = request._request.GET.get('password')
user_obj = UserInfo.objects.filter(username=username, password=password).first()
if not user_obj:
result['code'] = '1001'
result['msg'] = "用户不存在"
# 如果不存在返回不存在异常
token = md5(username)
# 创建函数生成token(随机字符串)
result['token'] = token
UserToken.objects.update_or_create(user=user_obj, defaults={'token': token})
# 如何实例化对象存在,则创建或者更新token
except Exception as e:
result['code'] = '1002'
result['msg'] = '请求异常'
return JsonResponse(result)
# 创建处理request的视图函数
class OrderView(APIView):
authentication_classes = [MyAuthentication,]
def get(self, request, *args, **kwargs):
result = {
'code': '1003',
'msg': None,
'data': None
}
try:
result['data'] = ORDER_DICT
except Exception as e:
result['code'] = '1004',
result['msg'] = '请求错误'
return result
Note
在上边自定义的程序中,基本逻辑是:
- 首先是创建认证视图类,这个类解决的是哪些用户可以访问和获取到数据,认证视图中的思路是: dispatch调度方法获取到request后,进行加工,从加工的request中可以的到原生request通过post方法传过来的username和password信息,通过这些信息调用数据库查找匹配对象,如果没有抛出异常,如果存在,需要设置一个函数生成一个专属token
- 创建生成token函数,该函数需要用到time和hashlib两个第三方库,以request传过来的username和传入时间为参数进行设置生成
- 收到生成的token后认证视图将token作为参数返回,同时创建或者更新实例化对象的token字段信息,在用户再次登陆后传过来的信息中就自动包含token
- 创建处理request的视图类,视图类中调用已经自定义好的authentication_classes,这个类专门用于认证信息,在该类中接收到token信息,并与数据库中的验证,如果验证不一致,抛出异常,反之,则返回一个元组信息,并继续执行视图类。需要注意的是,authentication_classes中可以存在多个自定义的认证类,但一般用使用的都是一个。
- 验证成功后dispatch调度方法执行对应的方法,并返回值给前端页面。
框架内置的认证类
- BaseAuthentication
- BaseAuthentication类中是两个方法authenticate()和authenticate_header(), 我们在自定义认证类的时候需要继承自基类,并且对这两个进行重写,如果不重写,系统自动抛出异常。
- 其他认证类:BasicAuthentication认证
一般程序中用到的是我们自定义的认证类来进行开发
自定义认证类的使用方式
方式一:全局使用,需要在settings.py文件中设置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# 'rest_framework.authentication.BasicAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
'api.views.Authentication'
# 这里是通过路径的方式把自定义的认证类加载到全局文件中 ]
}
方式二:局部使用,需要在视图类中调用具体的自定义认证类
class OrderView(APIView):
'''
用于订单相关业务
'''
authentication_classes = [Authentication,]
def get(self, request, *args, **kwargs):
result = {
'code': '1000',
'msg': None,
'data': None
}
try:
result['data'] = ORDER_DICT
except Exception as e:
result['code': '1001']
result['msg': '访问出错']
return JsonResponse(result)
rest_framework框架之认证功能的使用和源码实现流程分析的更多相关文章
- Django Rest framework 框架之认证使用和源码执行流程
用这个框架需要先安装: pip3 install djangorestframework 如果写了一个CBV的东西,继承了View. # 继承Django里面View class APIView(Vi ...
- rest_framework框架的认证、权限
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth", ] ...
- Android多媒体框架总结(1) - 利用MediaMuxer合成音视频数据流程分析
场景介绍: 设备端通过服务器传向客户端(Android手机)实时发送视频数据(H.264)和音频数据(g711a或g711u), 需要在客户端将音视频数据保存为MP4文件存放在本地,用户可以通过APP ...
- 阶段3 1.Mybatis_04.自定义Mybatis框架基于注解开发_2 回顾自定义mybatis的流程分析
- 源码怎么找之rest_framework的用户认证
首先得有一点常识,比如用户认证,就是authenticate 比如一个函数,应该有返回值, 比如一个类里面的self,真的是代表本身这个类吗 再比如看到一个东西加括号,就两种情况,一种是函数,一种是类 ...
- django框架-DRF工程之认证功能
1.在Rest framework中进行了一系列的封装,这个认证功能也是被封装到在DRF工程中的一种,想要使用,首先需要在配置文件中进行相应的配置 REST_FRAMEWORK = { ’DEFAUL ...
- rest_framework框架下的Django声明和生命周期
rest_framework框架下的Django声明和生命周期 Django声明周期(request) 客户端发起请求 请求经过wsgi wsgi: 是一个协议----web服务网关接口,即在web服 ...
- JAVAEE——BOS物流项目10:权限概述、常见的权限控制方式、apache shiro框架简介、基于shiro框架进行认证操作
1 学习计划 1.演示权限demo 2.权限概述 n 认证 n 授权 3.常见的权限控制方式 n url拦截权限控制 n 方法注解权限控制 4.创建权限数据模型 n 权限表 n 角色表 n 用户表 n ...
- DRF框架之认证组件用法(第四天)
1. 什么是drf 框架的认证组件: auth 就等于是jango中的Auth模块,Auth是自带session信息,但是 drf的认证组件可以自定义token携带过去,去判断用的 2.如何实现认证呢 ...
随机推荐
- Hibernate错误——No row with the given identifier exists
错误 是用的是Hibernate自动建立的数据表,在进行数据库操作时,出现错误No row with the given identifier exists 解决 关系数据库一致性遭到了破坏,找到相关 ...
- nodeJs学习-02 fs模块(文件操作)
读文件: const fs = require('fs'); //读文件(异步) readFile(文件名,回调函数) fs.readFile('section03/testData/aaa.txt' ...
- python基础---字符串常用方法汇总
s3 = '123's2 = ' 's1 = 'This Is \t Cash's='abcdefghijklmnopqrstuvwxyz's4 = "0000000this is stri ...
- 一维数组的初始化及遍历 Day06
package com.sxt.arraytest1; import java.util.Arrays; /* * 一维数组 */ public class ArrayTest2 { public s ...
- Gym - 101617D_Jumping Haybales(BFS)
Sample Input 4 2 .### #... .#.. #.#. 3 1 .#. .#. .#. Sample Output 4 -1 题意:给一个n*n的图,每次最多能跳k个格子,只能向南( ...
- mysql数据库之去重
利用 distinct :对需要处理的字段进行去重 select distinct 字段名 from 表名 去重以后 利用group by select * from 表名 group by 字段名 ...
- linux下重启oracle数据库
如何在linux下重启oracle数据库 | 浏览:3930 | 更新:2013-09-18 19:33 1 2 3 4 5 6 分步阅读 在实际工作项目中,有时候会遇到需要对oracle数据库进行重 ...
- Streamy 解决办法
- HDFS概念名称节点和数据节点-名称节点-文件系统元数据的持久状态
- @hdu - 5503@ EarthCup
目录 @description@ @solution@ @accepted code@ @details@ @description@ n 个队伍两两之间比赛,保证没有平局. 现在给出 n 个队伍分别 ...