三、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实体。