三、Authentication & sessionid

客户在访问Django的某些敏感资料时,被要求需要先登录,客户通过/admin/login进行登录,客户登录成功后,Django给客户分配一个sessionid,后续的访问过程,客户端只需在http头部的cookie中携带该sessionid即可完成认证,无需每次都携带用户名和密码。

因此这里需要完成下面这些工作:

1、首次登陆(/admin/login),如何获取用户名和密码,并进行认证和登录?

2、用户认证,登录后,如何将用户名(userid)生成sessionid,并返回个客户?

3、用户再次登录时,在cookie中携带返回的sessionid,Django如何将sessionid转换为对应的userid?

1.1       类图

先来看看涉及到的类图及其关系图。首当其冲的是HTTPRequest(request)这个类,前面有介绍过,这里重点关注该模块涉及到的COOKIES,session和user。COOKIES主要以字典形式存储了从HTTP头部解析到的cookie信息,包括csrftoken,sessionid等信息。

Session是对应SessionStore类,session提供多个引擎可供使用,引擎实质是不同的存储机制,Django提供了多达五个session引擎,分别为:file,db,cached_db,cache,base,这些引擎都存储在django/contrib/sessions/backends中,通过global_setting.py(或者setting.py)进行设置和选择。以db引擎为例, 其主要涉及到的成员和函数有:

 

SessionStore存储形式主要以哈希表的形式存在,即:键:键值,常见的键及其键值如下:

KEY/宏

KEY

MEANING

SESSION_KEY

_auth_user_id

Username

BACKEND_SESSION_KEY

_auth_user_backend

django.contrib.auth.backends.ModelBackend,提供认证,鉴权机制

HASH_SESSION_KEY

_auth_user_hash

对应auth_session表中Session_data,基于用户密码哈希运算得到

request.session.session_key 存储了http解析到的以及更新后的sessionid。

django_session数据表中存在三个字段,分别为session_key,session_data,expire_data。其中session_key为对应的sessionid(request.session.session_key),而session_data是[_auth_user_id, _auth_user_backend, _auth_user_id]组成的字典经过base64加密后的结果。

User模型主要基于models.AbstractUser类。

 

1.2       首次认证以及登录过程

客户通过在浏览器中输入POST http://server_ip:server_port/admin/login, 并且在httpbody里面携带用户名和密码后发起认证过程。Admin模块通过url匹配进入:django. Contrib. admin. sites.login。

AdminSite.login(self, request, extra_context=None)à
   return login(request, **defaults)à
      form = authentication_form(request, data=request.POST)
      if form.is_valid():     /*判断用户输入是否有效*/
          auth_login(request, form.get_user())       /*登录*/
          return HttpResponseRedirect(request, redirect_to) /*登录成功跳转页面*/
 
      return TemplateResponse(request, template_name, context)/*其余情况呈现login界面*/
 
login(request, user, backend=None)à
  session_auth_hash = user.get_session_auth_hash()  /*生成hash */
/*To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.*/
  if SESSION_KEY in request.session:
     _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)):
  else: request.session.cycle_key() /*Creates a new session key, while retaining the current session data.*/
        
/*存储当前session值,主要是 userid,backend, session_auth_hash*/
  request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
  
rotate_token(request)  /*每次重新登陆后,更新csrftoken*/
 

request.session.cycle_key()这一步骤生成新的sessionid, 保存在request.session.session_key中,剩下的工作就是通过SessionMiddleware的process_response()调用将生成的sessionid以及其对应的属性(如expire等)传递给客户,客户下次请求直接在cookie携带该sessionid即可,而无需每次都携带用户名和密码等信息。

1.3       再次认证过程

客户在前面登录的基础上,再次访问django服务,django在解析到sessionid后,将sessionid转换为对应的userid,即完成认证过程。这里依赖两个中间件来实现,分别为:SessionMiddleware和AuthenticationMiddleware。

SessionMiddleware最先对到来的http请求进行处理,将从头部解析到的到的sessionid复制到request.session.session_key,并初始化一个SessionStore实体后赋值给request.session。

def process_request(self, request):

session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)

request.session = self.SessionStore(session_key)

AuthenticationMiddleware接着对到来的http请求进行处理,获取到用户信息,具体通过:

def process_request(self, request):

   request.user = 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

下面来看看django.contrb.auth中get_user()如何实现从sessionid到userid的转换的。

def get_user(request):

  user_id = _get_user_session_key(request)

  backend_path = request.session[BACKEND_SESSION_KEY]

  user = backend.get_user(user_id)

  # Verify the session做一下简单的校验

if hasattr(user, 'get_session_auth_hash'):

    session_hash = request.session.get(HASH_SESSION_KEY)

    session_hash_verified = session_hash and constant_time_compare(

        session_hash,

        user.get_session_auth_hash()

    )

    if not session_hash_verified:

        request.session.flush()

        user = None

  return user or AnonymousUser()

前面有介绍过在django_session数据表中的session_data字段,存储了(_auth_user_id, _auth_user_backend, _auth_user_id)这三个信息,因此通过session_key(/sessionid)可以方便的查询到_auth_user_id,即对应的userid。通过userid获取user实体就更简单了,因为userid是auth_user表的主键,通过主键值可以快捷的获取user实体。

原文地址:https://www.cnblogs.com/fbli/p/5925075.html