django修改密码强制退出机制

ぐ巨炮叔叔 提交于 2019-12-11 17:46:00

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

起因

BUG出现

系统升级django版本后经常出现自动退出登录

问题复现

系统升级django(大版本,如1.8、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中附带cookie

成功登录后的请求头

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并校验是否有效
	# ... 无关部分省略
)

寻根溯源

具体过程比较曲折,一言难尽。

会话保持依赖两个中间件SessionMiddlewareAuthenticationMiddleware,其中AuthenticationMiddleware通过request.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默认存储的如下信息:

{
	'_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开始移除此中间件并强制启用此特性

  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	# 所有系统指定此值为一样,能使密码密文不变
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!