【推荐】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('/')
退出
from django.contrib.auth import authenticate, login , logout
def logout(request):
logout(request) # 清除response的cookie和django_session中记录
return HttpResponseRedirect('/login')
保持登陆状态
成功登录后浏览器将在每次请求时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并校验是否有效
# ... 无关部分省略
)
寻根溯源
具体过程比较曲折,一言难尽。
会话保持依赖两个中间件SessionMiddleware
和AuthenticationMiddleware
,其中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_hash
和user.get_session_auth_hash()
是否相同
django_session
中session_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开始移除此中间件并强制启用此特性
- 当用户密码被修改后,hash变化,判断为session失效
- 当不同版本django中
authenticate()
时会自动重新生成密码的密文,导致数据库中密码密文变化,hash也变化,判断为session失效,这就是文章开始bug的原因
升级django密码密文变化原因d
绕过修改密码强制退出的方法
两种方法
方法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 # 所有系统指定此值为一样,能使密码密文不变
来源:oschina
链接:https://my.oschina.net/u/947688/blog/2120701