Django Session And Auth Middleware
前言:
一个Django App(app2)需要使用另外一个Django App(app1)的session和用户系统进行登录认证.
app1使用Django自带的SessionMiddle和AuthenticationMiddle,下面我结合Django源码和需求,
分析从带session_key的请求,获得用户的过程.
app1的settings如下:
SECRET_KEY = '*(&^&&JSIIJIFEJIFJ'
INSTALL_APPS = (
'django.contrib.sessions'
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': DB_FILE_PATH
}
}
# Session backend
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'从请求中获取用户的过程:
SessionMiddleware从request取得session_key,
然后根据session_key在数据库表Django_session中加载session数据(包含_auth_user_id),把sesion data设值到request
AuthenticationMiddleware从上一步的session中取得_auth_user_id,根据这个id从数据库用户表加载user,把user设值到request.
先看看SessionStore load session的过程:
# django.contrib.sessions.backends.cached_db
Class SessionStore(DBStore):
def load(self):
try:
data = self._cache.get(self.cache_key)
except Exception:
# Some backends (e.g. memcache) raise an exception on invalid
# cache keys. If this happens, reset the session. See #17810.
data = None
if data is None:
# Duplicate DBStore.load, because we need to keep track
# of the expiry date to set it properly in the cache.
try:
s = self.model.objects.get(
session_key=self.session_key,
expire_date__gt=timezone.now()
)
data = self.decode(s.session_data)
self._cache.set(self.cache_key, data, self.get_expiry_age(expiry=s.expire_date))
except (self.model.DoesNotExist, SuspiciousOperation) as e:
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
logger.warning(force_text(e))
self._session_key = None
data = {}
return data# django.contrib.sessions.backends.db
Class SessionStore(SessionBase):
def load(self):
try:
s = self.model.objects.get(
session_key=self.session_key,
expire_date__gt=timezone.now()
)
return self.decode(s.session_data)
except (self.model.DoesNotExist, SuspiciousOperation) as e:
if isinstance(e, SuspiciousOperation):
logger = logging.getLogger('django.security.%s' % e.__class__.__name__)
logger.warning(force_text(e))
self._session_key = None
return {}从load函数可以看出,cache_db.py的SessionStore先从caches里边根据cache_key找session data,而且当key不存在于cached backend时, 要求返回None,比如memcached backend返回空字典,导致无法从数据库获取session 信息,所以要把session cached backend禁用掉, 或者换一个返回None的backend,或者替换这个cacehd backend。
如果找不到才会从数据库里找(通过ORM),而db.py的SessionStore是直接从数据库里边加载session data的
另外
self.decode(s.session_data)
追踪下去得到验证代码如下:
# django.utils.crypto
def salted_hmac(key_salt, value, secret=None):
"""
Returns the HMAC-SHA1 of 'value', using a key generated from key_salt and a
secret (which defaults to settings.SECRET_KEY).
A different key_salt should be passed in for every application of HMAC.
"""
if secret is None:
secret = settings.SECRET_KEY
key_salt = force_bytes(key_salt)
secret = force_bytes(secret)
# We need to generate a derived key from our base key. We can do this by
# passing the key_salt and our base key through a pseudo-random function and
# SHA1 works nicely.
key = hashlib.sha1(key_salt + secret).digest()
# If len(key_salt + secret) > sha_constructor().block_size, the above
# line is redundant and could be replaced by key = key_salt + secret, since
# the hmac module does the same thing for keys longer than the block size.
# However, we need to ensure that we *always* do this.
return hmac.new(key, msg=force_bytes(value), digestmod=hashlib.sha1)上面代码可以看到,hmac使用一个key对value进行加密,而这个key的一部分就是Django App的SECRET_KEY, 关于SECRET_KEY的用途参考: Django-setting-SECRET_KEY
AuthenticationMiddle中也有一个HMAC校验的过程:
# django.contrib.auth.__init__
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)
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)
# 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()所以如果要实现App2使用App1的session系统,可以的做法是:
- 数据库设置一致
- SECRET_KEY和app1一致
- 如果cached backend当key不存在时不是返回None的话(比如memcached返回空字典{}),那app2不能使用带缓存的SessionStore backend.
或者改写SessionMiddleware和AuthenticationMiddle,把HAMC校验部分去掉.