django源码分析

本文环境python3.5.2,django1.10.x系列

1.这次分析django框架中登陆认证与接口权限检查。
2.在后端开发中,难免会对接口进行权限验证,其中对于接口是否登陆的验证是比较基础和重要的功能,有些与用户密切相关的接口必须要用户登陆后才能访问并获取数据,目前检查接口是否访问者登陆基本上都是利用会话保持来实现的。
3.大致浏览功能后我们分析一下Django框架中的认证的实现。

分析

Django认证及保持的功能的大致实现思路是当后端接口收到登陆的用户名与密码后,先去验证用户名与密码是否正确,如果正确则将数据库中user对应的pk放入会话中存储,然后当下一个接口获取信息访问时,就直接通过pk来获取user中的信息。

user = auth.authenticate(username=account, password=password)
auth.login(request, user)

此时我们来分析django.contrib.auth.init.py的大致内容,

def authenticate(**credentials):"""If the given credentials are valid, return a User object."""for backend, backend_path in _get_backends(return_tuples=True):try:inspect.getcallargs(backend.authenticate, **credentials)                                    # 检查backend.authenticate中的参数是否正确except TypeError:# This backend doesn't accept these credentials as arguments. Try the next one.continuetry:user = backend.authenticate(**credentials)                                                  # 调用认证该函数except PermissionDenied:# This backend says to stop in our tracks - this user should not be allowed in at all.breakif user is None:                                                                                # 如果user为空跳过continue# Annotate the user object with the path of the backend.user.backend = backend_path                                                                     # 把认证backend的路径存入userreturn user                                                                                     # 返回用户# The credentials supplied are invalid to all backends, fire signaluser_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials))

authenticate函数就是验证用户名密码是否正确的函数,如果验证成功就返回查询出来的user对象,其中是通过_get_backends函数来获取查询的模板

def _get_backends(return_tuples=False):backends = []for backend_path in settings.AUTHENTICATION_BACKENDS:                                   # 配置中的认证模板路径backend = load_backend(backend_path)                                                # 返回导入模板路径的模板实例backends.append((backend, backend_path) if return_tuples else backend)              # 判断返回是否包含实例与相应路径if not backends:raise ImproperlyConfigured('No authentication backends have been defined. Does ''AUTHENTICATION_BACKENDS contain anything?')return backends                                                                         # 返回生成模板列表

其中settings.AUTHENTICATION_BACKENDS就是配置的认证模板的模板,该配置项如果没有配置则使用默认的配置,

AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']

其中ModelBackend的分析如下

class ModelBackend(object):"""Authenticates against settings.AUTH_USER_MODEL."""def authenticate(self, username=None, password=None, **kwargs):UserModel = get_user_model()                                                        # 获取user的modelif username is None:username = kwargs.get(UserModel.USERNAME_FIELD)                                 # 如果没有显示传入,则去kwarg中配置的USERNAME_FIELD字段值try:user = UserModel._default_manager.get_by_natural_key(username)                  # 通过用户名获取userexcept UserModel.DoesNotExist:# Run the default password hasher once to reduce the timing# difference between an existing and a non-existing user (#20760).UserModel().set_password(password)                                              else:if user.check_password(password) and self.user_can_authenticate(user):          # 调用user来检查密码是否正确return userdef get_user(self, user_id):UserModel = get_user_model()                                                        # 获取用户表try:user = UserModel._default_manager.get(pk=user_id)                               # 通过查表获取用户userexcept UserModel.DoesNotExist:return Nonereturn user if self.user_can_authenticate(user) else None                           # 返回内容

只分析了该类的这两个方法,此时就调用了authenticate方法来验证用户名与密码是否正确,此时需要去获取User表如果没有配置则会使用默认的配置

AUTH_USER_MODEL = 'auth.User'

此时会调用user表中的check_password中来检查,该检查大家可以自行到base_user.py中去查看内容,在此不作详细分析。
如果检查正确则返回user

返回user成功后接着执行

auth.login(request, user)

我们继续分析django.contrib.auth.init.py中,login的相关代码

def login(request, user, backend=None):"""Persist a user id and a backend in the request. This way a user doesn'thave to reauthenticate on every request. Note that data set duringthe anonymous session is retained when the user logs in."""session_auth_hash = ''                                                                              # 会话认证hash值if user is None:user = request.user                                                                             # 如果用户则设置为request的user,因为进过了auth的中间价处理所以肯定有user属性if hasattr(user, 'get_session_auth_hash'):                                                          # 如果有get_session_auth_hash方法则调用该方法生成hash值session_auth_hash = user.get_session_auth_hash()if SESSION_KEY in request.session:                                                                  # 检查该key是否在会话中存在值if _get_user_session_key(request) != user.pk or (session_auth_hash andnot constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):   ## To avoid reusing another user's session, create a new, empty# session if the existing session corresponds to a different# authenticated user.request.session.flush()else:request.session.cycle_key()                                                                     # 重新加载和生成新的session保持该会话数据try:backend = backend or user.backend                                                               # 如果传入有指定值,则使用指定值否则使用user的backendexcept AttributeError:backends = _get_backends(return_tuples=True)                                                    # 如果user没有改属性,获取所有的backendsif len(backends) == 1:_, backend = backends[0]else:raise ValueError('You have multiple authentication backends configured and ''therefore must provide the `backend` argument or set the ''`backend` attribute on the user.')request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)                                  # 将该值存为user的pkrequest.session[BACKEND_SESSION_KEY] = backend                                                      # 用户的认证模板request.session[HASH_SESSION_KEY] = session_auth_hash                                               # 设置用户的会话hash值if hasattr(request, 'user'):                                                                        # request是否有user属性,有就设置request.user = userrotate_token(request)                                                                               # 设置跨站请求cookieuser_logged_in.send(sender=user.__class__, request=request, user=user)

该方法大致执行流程是,先生成会话的hash值,然后判断该会话是否与其他已经存在的值发生了冲突,如果没有冲突则重新生成一个新的会话并将旧数据保存到新会话中,然后将查询出来的user的id,通过user的id获取user信息的backend,会话的哈希值存入会话中,然后给request设置user属性,此时登录验证的操作就执行完成。

该次接口的用户数据验证后,接下来是怎样在下一次接口登录的时候,获取已经登录的用户信息呢?Django是通过django.contrib.auth.middleware.AuthenticationMiddleware中间件来完成的,分析如下


class AuthenticationMiddleware(MiddlewareMixin):def process_request(self, request):assert hasattr(request, 'session'), ("The Django authentication middleware requires session middleware ""to be installed. Edit your MIDDLEWARE%s setting to insert ""'django.contrib.sessions.middleware.SessionMiddleware' before ""'django.contrib.auth.middleware.AuthenticationMiddleware'.") % ("_CLASSES" if settings.MIDDLEWARE is None else "")request.user = SimpleLazyObject(lambda: get_user(request))              # 直接设置user的属性为延迟对象,当获取user的值时,再求值并保存

其中SimpleLazyObject如下:

class SimpleLazyObject(LazyObject):"""A lazy object initialized from any function.Designed for compound objects of unknown type. For builtins or objects ofknown type, use django.utils.functional.lazy."""def __init__(self, func):"""Pass in a callable that returns the object to be wrapped.If copies are made of the resulting SimpleLazyObject, which can happenin various circumstances within Django, then you must ensure that thecallable can be safely run more than once and will return the samevalue."""self.__dict__['_setupfunc'] = func                          # 将传入的值设置为func,并保存下来super(SimpleLazyObject, self).__init__()def _setup(self):self._wrapped = self._setupfunc()                           # 执行该方法的值设置为该方法的属性

只分析了其中这一段,作用就是将传入的方法,放入_setupfunc属性中,当调用_setup时就执行传入的方法,我们继续查看LazyObject是怎样操作的

def new_method_proxy(func):def inner(self, *args):if self._wrapped is empty:                  # 如果被延迟的值为空self._setup()                           # 调用_setup方法return func(self._wrapped, *args)           # 调用func进行求值return innerclass LazyObject(object):"""A wrapper for another class that can be used to delay instantiation of thewrapped class.By subclassing, you have the opportunity to intercept and alter theinstantiation. If you don't need to do that, use SimpleLazyObject."""# Avoid infinite recursion when tracing __init__ (#19456)._wrapped = Nonedef __init__(self):# Note: if a subclass overrides __init__(), it will likely need to# override __copy__() and __deepcopy__() as well.self._wrapped = empty                                           # 初始化被懒计算的值__getattr__ = new_method_proxy(getattr)                             # 重新改写获取属性的操作,如果_wrapped没有值则计算第一次缓存改值,def __setattr__(self, name, value):                                 # 设置属性时,如果不是_wrapped字段值,都给_wrapped设置属性,否则替换_wrapped值if name == "_wrapped":# Assign to __dict__ to avoid infinite __setattr__ loops.self.__dict__["_wrapped"] = valueelse:if self._wrapped is empty:self._setup()setattr(self._wrapped, name, value)def __delattr__(self, name):if name == "_wrapped":raise TypeError("can't delete _wrapped.")                   # 如果删除的属性为_wrapped禁止删除if self._wrapped is empty:self._setup()delattr(self._wrapped, name)                                    # 都删除_wrapped的属性值def _setup(self):"""Must be implemented by subclasses to initialize the wrapped object."""raise NotImplementedError('subclasses of LazyObject must provide a _setup() method')

该类就是当该属性已经求值一次后,后面所有对该对象的操作都是基于求值一次后保存的值,以此达到缓存求值的目的。

通过对SimpleLazyObject分析可知,其实质还是对get_user(request)进行求值。

def get_user(request):if not hasattr(request, '_cached_user'):request._cached_user = auth.get_user(request)       # 当求值时调用该对象return request._cached_user

我们继续在init.py中,分析get_user函数:

def get_user(request):"""Returns the user model instance associated with the given request session.If no user is retrieved an instance of `AnonymousUser` is returned."""from .models import AnonymousUseruser = None                                                                                         # 获取用户try:user_id = _get_user_session_key(request)                                                        # 获取缓存到会话数据中的user_idbackend_path = request.session[BACKEND_SESSION_KEY]                                             # 获取会话数据中的认证模板路径except KeyError:passelse:if backend_path in settings.AUTHENTICATION_BACKENDS:                                            # 判断缓存数据中的路径是否在配置的模板路径中backend = load_backend(backend_path)                                                        # 加载模板user = backend.get_user(user_id)                                                            # 通过user_id获取user# Verify the sessionif hasattr(user, 'get_session_auth_hash'):                                                  # 检查用户是否拥有get_session_auth_hashsession_hash = request.session.get(HASH_SESSION_KEY)                                    # 获取当前session的会话hashsession_hash_verified = session_hash and constant_time_compare(                         # 如果有值然后比较会话中的hash与user获取的hashsession_hash,user.get_session_auth_hash())if not session_hash_verified:request.session.flush()                                                             # 如果验证失败则会话置空user = Nonereturn user or AnonymousUser()   

先获取该请求会话中的user_id和会话的哈希值,以此来进行通过会话中保存的backend来查询user_id对应的user,由此来获取用户的信息,此时我们就可以使用request.user来查询登录用户的值。
至此,Django的框架的认证与登录保持功能就分析完毕,这其中还没有涉及到auth中大量的细节,如有兴趣可自行查看。

Django源码分析6:auth认证及登陆保持相关推荐

  1. Django源码分析5:session会话中间件分析

    django源码分析 本文环境python3.5.2,django1.10.x系列 1.这次分析django框架中的会话中间件. 2.会话保持是目前框架都支持的一个功能,因为http是无状态协议,无法 ...

  2. Django源码分析4:staticfiles静态文件处理中间件分析

    django源码分析 本文环境python3.5.2,django1.10.x系列1.在上一篇文章中已经分析过handler的处理过程,其中load_middleware就是将配置的中间件进行初始化, ...

  3. Django源码分析10:makemigrations命令概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-makemigrations命令概述 Django项目中的数据库管理命令就是通过makemig ...

  4. Django源码分析9:model.py表结构的初始化概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-model概述 Django项目中提供了内置的orm框架,只需要在models.py文件中添加 ...

  5. Django源码分析8:单元测试test命令浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-test命令分析 Django项目中提供了,test命令行命令来执行django的单元测试,该 ...

  6. Django源码分析7:migrate命令的浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-migrate命令分析 Django项目中提供了,通过migrations操作数据库的结构的命 ...

  7. Django源码分析3:处理请求wsgi分析与视图View

    django源码分析 本文环境python3.5.2,django1.10.x系列 根据前上一篇runserver的博文,已经分析了本地调试服务器的大致流程,现在我们来分析一下当runserver运行 ...

  8. Django源码分析2:本地运行runserver分析

    django源码分析 本文环境python3.5.2,django1.10.x系列1.根据上一篇文章分析了,django-admin startproject与startapp的分析流程后,根据dja ...

  9. Django源码分析1:创建项目和应用分析

    django命令行源码分析 本文环境python3.5.2,django1.10.x系列 当命令行输入时django-admin时 (venv) ACA80166:dbManger wuzi$ dja ...

最新文章

  1. 手机号码输入历史记录匹配
  2. python3.8 新特性
  3. 6. Qt 信号与信号槽 (7)-QMetaObject:: activate
  4. 运行测试Caused by: java.lang.UnsatisfiedLinkError: no attach in java.library.path错误解决
  5. Java后台管理系统,开箱即用
  6. javaweb实现文件上传,前端与后台的结合实现
  7. 调试记录- error: #error “must enable c++17“
  8. Bootstrap导航组件
  9. HDU 3315 My Brute
  10. 进程之 回收子进程之避免僵尸进程的产生
  11. LabView学习笔记(九):数组与簇
  12. 如何求取管壁努塞尔数【转载】
  13. ffiddler抓取手机(app)https包
  14. 【soft6星评论】伯俊软件借用中台撬动新一轮互联网化
  15. 【go-zero】go-zero开发环境 如何聚合所有api? caddy反向代理服务分发 微服务设计api聚合方法 best practice
  16. c# 扫描局域网IP列表的几种方法
  17. 手机拍照打卡活动制作方案,通过拍照不聚集活动,函数参数(Function parameters)是在函数定义中所列的名称。
  18. 恭喜ULAM团队在数字版权领域的研究上有了新突破
  19. 汽车行业OTD业务模式入门学习
  20. 「软工博客作业」:QQ音乐VS网易云音乐

热门文章

  1. (送)Java 架构技术揭秘:Redis+Nginx+Dubbo精选+面试题+精选视频
  2. Arm 十年重磅发布 v9 架构,不受 EAR 约束,未来将覆盖 3000 亿颗芯片
  3. 不追逐标准化产品,360数科的一站式风控体系有何不同?
  4. 有了图分析,可解释的AI还远吗?
  5. 赠书 | Python人脸五官姿态检测
  6. 关于GCN,我有三种写法
  7. 树莓派4与英伟达Jetson Nano性能大比拼,谁是最佳的嵌入式“电脑”?
  8. Python三十年技术演变史
  9. 程序员,快通知你们老板上吴恩达的最新AI课
  10. 报名 | 美团是怎样给你推荐外卖的?美团大脑知识图谱详解