系統升級django版本後常常出現自動退出登陸python
系統升級django(大版本,如1.八、1.11和2.0)後,舊版與新版同時運行,同一各User用舊版authenticate驗證後會致使新版中已登陸User被退出。數據庫
from django.contrib.auth import authenticate, login , logout def login(request): if request.method == "POST": username = request.POST.get('username', '') password = request.POST.get('password', '') user = authenticate(username=username, password=password) # 驗證用戶賬號密碼是否正確,經過後返回User對象 if not user: login(request, user) # 登陸,response添加cookie並將session_key記錄到django_session request.session.set_expiry(0) # 設置session失效時間,0表示關閉瀏覽器失效 return HttpResponseRedirect('/')
from django.contrib.auth import authenticate, login , logout def logout(request): logout(request) # 清除response的cookie和django_session中記錄 return HttpResponseRedirect('/login')
成功登陸後瀏覽器將在每次請求時Header中附帶cookiedjango
settings.py
配置:瀏覽器
SECRET_KEY = '%iai=j+uyd4s0t3qm$3w-w6^b0lf!df%hh-@5f!0&4enc(o7#5' # key變化密碼的hash也會變 MIDDLEWARE = ( # ... 無關部分省略 'django.contrib.sessions.middleware.SessionMiddleware', # 獲取cookie中sessionid對應的django_session,有效的時候會把session對象增長到request.session 'django.contrib.auth.middleware.AuthenticationMiddleware', # 根據request.session獲取對應User並校驗是否有效 # ... 無關部分省略 )
具體過程比較曲折,一言難盡。cookie
會話保持依賴兩個中間件SessionMiddleware
和AuthenticationMiddleware
,其中AuthenticationMiddleware
經過request.session獲取用戶對象,並比對此時用戶認證和session中存儲的認證是否相同session
HASH_SESSION_KEY = '_auth_user_hash' # auth.get_user def get_user(request): """ Return the user model instance associated with the given request session. If no user is retrieved, return an instance of `AnonymousUser`. """ 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()
也就是session_hash_verified = session_hash and constant_time_compare(session_hash,user.get_session_auth_hash())
,對比session._auth_user_hash
和user.get_session_auth_hash()
是否相同 django_session
中session_data
默認存儲的以下信息:code
{ '_auth_user_id': '25', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': '54332f14831144a4022e7601c6dd3fcc531a5454', '_session_expiry': 0 }
user.get_session_auth_hash()
以下(User的父類):中間件
class AbstractBaseUser(models.Model): def get_session_auth_hash(self): """ Return an HMAC of the password field. """ key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" return salted_hmac(key_salt, self.password).hexdigest()
那麼影響hash的就是user.password,django這麼作本來是要實現修改密碼強制用戶自動退出。django文檔對象
修改密碼強制用戶自動退出的特性在django 2.0以前版本須要添加
'django.contrib.auth.middleware.SessionAuthenticationMiddleware'
到MIDDLEWARE_CLASSES
才能啓用此特性,2.0開始移除此中間件並強制啓用此特性blog
authenticate()
時會自動從新生成密碼的密文,致使數據庫中密碼密文變化,hash也變化,判斷爲session失效,這就是文章開始bug的緣由兩種方法
方法1: 每次請求不嚴重hash(僅適用於自定義User的) 屏蔽user.get_session_auth_hash()方法,使跳過hash比對
class User(AbstractBaseUser): # ... 無關的省略 def __getattribute__(self, item): if item == 'get_session_auth_hash': raise AttributeError return super().__getattribute__(item)
方法2: 保證不一樣版本密碼密文一致 settings.py
PASSWORD_HASHERS = [ 'core.password.CustomPBKDF2PasswordHasher', ]
core.password.py
from django.contrib.auth.hashers import PBKDF2PasswordHasher class CustomPBKDF2PasswordHasher(PBKDF2PasswordHasher): iterations = 20000 # 全部系統指定此值爲同樣,能使密碼密文不變