江湖

因为爱,所以爱

  • 主页
  • 归档
  • 目录
所有文章 友链 关于我

江湖

  • 主页
  • 归档
  • 目录

drf源码分析第五说--认证

2019-11-17
字数统计: 1.6k字   |   阅读时长: 7分

源码解析 – 认证

0x01 drf认证流程

之前在版本的时候了解到了django中CBV的执行流程,最后走到了dispatch()方法。寻找版本的时候,先看了一下通过Request类对当前的request对象进行封装,然后执行initial()方法,里面有版本值的获取方式,今天的认证也是基于dispatch,一步一步的向下走。

APIView dispatch

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
46
class APIView(View):

**5. 从配置中取出authentication_classes对应的值
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
def dispatch(self, request, *args, **kwargs):
**1. # ###################### 第一步 ###########################
"""
request,是django的request,它的内部有:request.GET/request.POST/request.method
args,kwargs是在路由中匹配到的参数,如:
url(r'^order/(\d+)/(?P<version>\w+)/$', views.OrderView.as_view()),
http://www.xxx.com/order/1/v2/
"""
self.args = args
self.kwargs = kwargs

**2. 通过Request封装
"""
request = 生成了一个新的request对象,此对象的内部封装了一些值。
request = Request(request)
- 内部封装了 _request = 老的request
- 内部封装了 authenticators = [MyAuthentication(), ]
"""
**6. 封装完之后返回给新的request
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?

try:

**7. 执行initial方法
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

initialize_request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)

return Request(
request,
parsers=self.get_parsers(),
**3. 对认证进行封装
authenticators=self.get_authenticators(), # [MyAuthentication(),]
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)

get_authenticators

1
2
3
4
5
6
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
**4. authentication_classes这个是从配置中取的值,取出值并对其实例化
return [ auth() for auth in self.authentication_classes ]

initial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class APIView(View):
def initial(self, request, *args, **kwargs):
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

**8. 执行下面的perform_authentication进行认证
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)

perform_authentication

1
2
3
4
5
6
class APIView(View):
def perform_authentication(self, request):

**9. 这儿调用了request.user,现在的request是Request封装的实例化对象,所以会跳转到Request().user
**18. 接收到了self._user,认证结束
request.user

Request().user

1
2
3
4
5
6
7
8
9
10
11
12
13
class Request:
**10. 把方法变成属性的一个装饰器
@property
def user(self):

**11. 先判断是否有_user属性,然后开启了一个上下文wrap_attributeerrors()
if not hasattr(self, '_user'):
**12. 先忽略它,先当它是成功的,然后执行下面的方法
with wrap_attributeerrors():
**17. 这儿就得到了self._user和self.auth
self._authenticate()

return self._user

wrap_attributeerrors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@contextmanager
def wrap_attributeerrors():
"""
Used to re-raise AttributeErrors caught during authentication, preventing
these errors from otherwise being handled by the attribute access protocol.
"""
try:
yield
except AttributeError:
info = sys.exc_info()
exc = WrappedAttributeError(str(info[1]))
raise exc.with_traceback(info[2])

这里的原函数是一个生成器,而with wrap_attributeerrors是一个用contextlib中的contextmanager装饰器装饰的一个上下文管理器,返回的是一个_GeneratorContextManager对象,而这个对象中重写了__enter__和__exit__方法,因此当with wrap_attributeerrors()时,调用的是_GeneratorContextManager中的__enter__,而这个方法里调用了原函数的next()方法,因此相当于启动了该生成器。然后回来执行上下文中的代码,如果出现异常,则会执行_GeneratorContextManager中的__exit__,也就是再执行一次make_context.next()函数,所以先会用自己所定义的异常处理方式来处理异常,然后再处理其他异常。

_authenticate

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Request:
def _authenticate(self):

**13. 这儿就开始循环了,循环第4步的authenticators实例化对象,
for authenticator in self.authenticators:
try:

**14. 寻找authenticate方法,实例化对象authenticator通过c3算法查找authenticate方法并执行,返回一个元组,所以说这儿就可以自己定义一个类,继承APIView,实现authenticate方法,自己对它进行验证,校验
user_auth_tuple = authenticator.authenticate(self)
**默认的authenticate方法
"""
class ForcedAuthentication:
"""
This authentication class is used if the test client or request factory
forcibly authenticated the request.
"""

def __init__(self, force_user, force_token):
self.force_user = force_user
self.force_token = force_token

def authenticate(self, request):
**force_user和force_token都可以通过Request这个类获取得到
'''
Request类
force_user = getattr(request, '_force_auth_user', None)
force_token = getattr(request, '_force_auth_token', None)
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
'''
return (self.force_user, self.force_token)
"""
except exceptions.APIException:
self._not_authenticated()
raise

if user_auth_tuple is not None:

**15. 对其封装,因为有值了,所以下次直接return
self._authenticator = authenticator

**16. user和auth就得到了对应的值,并返回给上一层
self.user, self.auth = user_auth_tuple
return

self._not_authenticated() **如过user_auth_tuple获取不到值,则会执行这个方法,都变成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

当用户发来请求时,找到认证的所有类并实例化成为对象列表,然后将对象列表封装到新的request对象中。

以后在视同中调用request.user

在内部会循环认证的对象列表,并执行每个对象的authenticate方法,该方法用于认证,他会返回两个值分别会赋值给
request.user/request.auth

差不多就是这些,再细的话,就是每行加注释了,如果都是注释的话,我是看不下去的。不过知道了总体流程的话,一些小细节我相信还是可以看懂的

0x02 认证示例

settings.py

1
2
3
4
5
6
7
8
9
REST_FRAMEWORK = {
"PAGE_SIZE":2,
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
"ALLOWED_VERSIONS":['v1',],
# "UNAUTHENTICATED_USER":None, # 不知道你们对这个眼熟吗?它是在_not_authenticated方法中用到了
# "UNAUTHENTICATED_TOKEN":None, # 这个也用到了
"DEFAULT_AUTHENTICATION_CLASSES":["api.extends.auth.HulaQueryParamAuthentication",],
}

api.extends.auth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from rest_framework.authentication import BaseAuthentication
from api import models


class LuffyAuthentication(BaseAuthentication):

def authenticate(self, request):
"""
auth
:param request:
:return:
"""
token = request.query_params.get('token')
if not token:
return (None, None)

user_object = models.UserInfo.objects.filter(token=token).first()
if user_object:
return (user_object, token)
return (None, None)

urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.conf.urls import url,include
from api import views

urlpatterns = [
url(r'^api/(?P<version>\w+)/', include('api.urls',namespace='api')),
]

# api.urls.py
from django.conf.urls import url
from api import views

urlpatterns = [
url(r'^category/$', views.CategoryView.as_view()),
url(r'^login/$', views.LoginView.as_view()),
]

models.py

1
2
3
4
5
6
7
8
9
10
11
12
from django.db import models


class UserInfo(models.Model):
"""用户表"""
username = models.CharField(verbose_name='用户名',max_length=32)
password = models.CharField(verbose_name='密码',max_length=64)
token = models.CharField(verbose_name='认证随机字符串', max_length=64, null=True, blank=True)


class Category(models.Model):
name = models.CharField(verbose_name="分类", max_length=16)

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from api import models
from rest_framework.views import APIView
from rest_framework.response import Response


class LoginView(APIView):
authentication_classes = []
permission_classes = []
"""
登录接口
"""
def get(self, request, *args, **kwargs):
return Response('order')


class CategoryView(APIView):
def get(self, request, *args, **kwargs):
data = models.Category.objects.all().values()
return Response(data)

1
2
3
分别请求两个链接,认证之后的才有数据
http://127.0.0.1/api/v1/category/
http://127.0.0.1/api/v1/category/?token=123456
  • python
  • drf
  • django
drf源码分析第七说 -- 节流/频率限制
drf源码分析第四说 -- 简单视图流程/api/版本管理
© 2020 江湖
Hexo Theme Yilia by Litten
 | 10093    | 9536
  • 所有文章
  • 友链
  • 关于我

tag:

  • python
  • flask
  • 目录
  • drf
  • django
  • restful
  • 技术
  • linux
  • 虚拟环境
  • nginx
  • saltstack
  • redis
  • 单例模式
  • websocket,django
  • 项目,drf,restful
  • 收藏夹
  • rpc
  • docker
  • rebbitmq
  • 随笔
  • 并发编程

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • django信号、缓存和读写分离

    2019-12-27

    #django

  • 2019-12-26

  • 干货链接/收藏夹

    2019-12-18

    #收藏夹

  • nginx+uwsig+crm+mysql+virtualenv+supervisor

    2019-12-15

    #linux#nginx

  • saltstack日常学习

    2019-12-15

    #linux#saltstack

  • rpc远程调用

    2019-12-12

    #linux#rpc

  • redis主从复制、哨兵和集群

    2019-12-12

    #linux#redis

  • 消息队列之rabbitmq学习使用及消息持久化

    2019-12-12

    #linux#rebbitmq

  • docker全部知识点总结

    2019-12-07

    #docker

  • redis持久化存储之AOF+RDB

    2019-12-02

    #linux#redis

  • 通过django+channels+websocket实现网络聊天室

    2019-11-25

    #websocket,django

  • drf系列总结目录

    2019-11-24

    #python#目录#drf#django#restful

  • flask剖析第六说--Flask源码之:路由加载

    2019-11-22

    #python#flask

  • flask剖析第四说--LocalStack和Local对象实现栈的管理

    2019-11-22

    #python#flask

  • flask剖析第二说--快速使用flask

    2019-11-22

    #python#flask

  • flask剖析第五说--Flask源码之:配置加载

    2019-11-22

    #python#flask

  • flask剖析第三说--threading.local和高级方法

    2019-11-22

    #python#flask

  • SqlHelper--单例模式

    2019-11-22

    #python#单例模式

  • flask剖析第一说--通过werkzurg了解wsgi

    2019-11-22

    #python#flask

  • drf源码分析第八说 -- Jwt认证

    2019-11-22

    #python#drf#django

  • linux--虚拟环境搭建python-django项目

    2019-11-22

    #linux#虚拟环境

  • git工具的使用

    2019-11-22

    #技术

  • 面试集锦

    2019-11-20

    #随笔

  • drf总结草稿

    2019-11-20

    #drf#django

  • drf源码分析第六说 -- 权限

    2019-11-19

    #python#drf#django

  • drf源码分析第七说 -- 节流/频率限制

    2019-11-19

    #python#drf#django

  • drf源码分析第五说--认证

    2019-11-17

    #python#drf#django

  • drf源码分析第四说 -- 简单视图流程/api/版本管理

    2019-11-17

    #python#drf#django

  • drf源码分析第三说--分页

    2019-11-16

    #python#drf#django

  • functools中偏函数及示例源码

    2019-11-15

    #flask

  • drf源码分析第二说 -- 快速上手drf

    2019-11-14

    #python#drf#django

  • drf源码分析第一说--restful规范

    2019-11-13

    #python#drf

  • 呼啦圈项目总结草稿

    2019-11-08

    #项目,drf,restful

  • flask请求生命周期

    2019-11-05

    #flask

  • Flask从入门到放弃目录

    2019-10-21

    #python#flask#目录

  • 进程

    2019-10-17

    #并发编程

  • 协程

    2019-10-17

    #并发编程

  • Hello World

    2019-08-22

  • my-first-blog

    2019-08-22

    #技术

  • CSDN
  • 博客园
  • 林殊途
遮浪加西浪啊,快哇波为刚啊
这条大路遮两崩啊,给里不噶两崩啊
嗯gia嗯gia 就嗯gia 蛙洗好桑gia
宏大驴大咧头大 我就西嘎发bia