django修改密碼強制退出機制

原由

BUG出現

系統升級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('/')

成功登陸時的response添加了cookie

退出

from django.contrib.auth import authenticate, login , logout
def logout(request):
	logout(request)	# 清除response的cookie和django_session中記錄
    return HttpResponseRedirect('/login')

退出登陸時的response清除了cookie

保持登錄狀態

成功登陸後瀏覽器將在每次請求時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

會話保持依賴兩個中間件SessionMiddlewareAuthenticationMiddleware,其中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_hashuser.get_session_auth_hash()是否相同 django_sessionsession_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

  1. 當用戶密碼被修改後,hash變化,判斷爲session失效
  2. 當不一樣版本django中authenticate()時會自動從新生成密碼的密文,致使數據庫中密碼密文變化,hash也變化,判斷爲session失效,這就是文章開始bug的緣由

升級django密碼密文變化緣由d

參考1 參考2

繞過修改密碼強制退出的方法

兩種方法

方法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	# 全部系統指定此值爲同樣,能使密碼密文不變
相關文章
相關標籤/搜索