Django源码分析6:auth认证及登陆保持
发布日期:2021-07-25 13:04:24 浏览次数:10 分类:技术文章

本文共 12983 字,大约阅读时间需要 43 分钟。

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.            continue        try:            user = backend.authenticate(**credentials)                                                  # 调用认证该函数        except PermissionDenied:            # This backend says to stop in our tracks - this user should not be allowed in at all.            break        if user is None:                                                                                # 如果user为空跳过            continue        # Annotate the user object with the path of the backend.        user.backend = backend_path                                                                     # 把认证backend的路径存入user        return user                                                                                     # 返回用户    # The credentials supplied are invalid to all backends, fire signal    user_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的model        if username is None:            username = kwargs.get(UserModel.USERNAME_FIELD)                                 # 如果没有显示传入,则去kwarg中配置的USERNAME_FIELD字段值        try:            user = UserModel._default_manager.get_by_natural_key(username)                  # 通过用户名获取user        except 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 user    def get_user(self, user_id):        UserModel = get_user_model()                                                        # 获取用户表        try:            user = UserModel._default_manager.get(pk=user_id)                               # 通过查表获取用户user        except UserModel.DoesNotExist:            return None        return 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't    have to reauthenticate on every request. Note that data set during    the 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 and                not 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的backend    except AttributeError:        backends = _get_backends(return_tuples=True)                                                    # 如果user没有改属性,获取所有的backends        if 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的pk    request.session[BACKEND_SESSION_KEY] = backend                                                      # 用户的认证模板    request.session[HASH_SESSION_KEY] = session_auth_hash                                               # 设置用户的会话hash值    if hasattr(request, 'user'):                                                                        # request是否有user属性,有就设置        request.user = user    rotate_token(request)                                                                               # 设置跨站请求cookie    user_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 of    known 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 happen        in various circumstances within Django, then you must ensure that the        callable can be safely run more than once and will return the same        value.        """        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 the    wrapped class.    By subclassing, you have the opportunity to intercept and alter the    instantiation. If you don't need to do that, use SimpleLazyObject.    """    # Avoid infinite recursion when tracing __init__ (#19456).    _wrapped = None    def __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"] = value        else:            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 AnonymousUser    user = None                                                                                         # 获取用户    try:        user_id = _get_user_session_key(request)                                                        # 获取缓存到会话数据中的user_id        backend_path = request.session[BACKEND_SESSION_KEY]                                             # 获取会话数据中的认证模板路径    except KeyError:        pass    else:        if backend_path in settings.AUTHENTICATION_BACKENDS:                                            # 判断缓存数据中的路径是否在配置的模板路径中            backend = load_backend(backend_path)                                                        # 加载模板            user = backend.get_user(user_id)                                                            # 通过user_id获取user            # Verify the session            if hasattr(user, 'get_session_auth_hash'):                                                  # 检查用户是否拥有get_session_auth_hash                session_hash = request.session.get(HASH_SESSION_KEY)                                    # 获取当前session的会话hash                session_hash_verified = session_hash and constant_time_compare(                         # 如果有值然后比较会话中的hash与user获取的hash                    session_hash,                    user.get_session_auth_hash()                )                if not session_hash_verified:                    request.session.flush()                                                             # 如果验证失败则会话置空                    user = None    return user or AnonymousUser()

先获取该请求会话中的user_id和会话的哈希值,以此来进行通过会话中保存的backend来查询user_id对应的user,由此来获取用户的信息,此时我们就可以使用request.user来查询登录用户的值。

至此,Django的框架的认证与登录保持功能就分析完毕,这其中还没有涉及到auth中大量的细节,如有兴趣可自行查看。

转载地址:https://blog.csdn.net/qq_33339479/article/details/79367908 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Python源码学习:启动流程简析
下一篇:Django源码分析5:session会话中间件分析

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月04日 10时59分02秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章