本文已参与「新人创作礼」活动,一起开启掘金创作之路。
在django中使用redis及前后端分离直接使用原有login、logout
基本信息
| 环境 | 版本 |
|---|---|
| python | 3.7.12 |
| django | 3.1.12 |
| django rest framework | 3.12.4 |
| django redis | 5.0.0 |
修改django
继承修改admin.py
from bm_system.models import User
from django.contrib import admin
from django.apps import apps
from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.core.exceptions import ValidationError
# Register your models here.
# 自动注册所有model
models = apps.get_models()
for model in models:
try:
admin.site.register(model)
except admin.sites.AlreadyRegistered:
pass
# 修改user创建表单
class UserCreationForm(forms.ModelForm):
password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name')
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise ValidationError("Passwords don't match")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
# 修改user修改表单
class UserChangeForm(forms.ModelForm):
"""A form for updating users. Includes all the fields on
the user, but replaces the password field with admin's
disabled password hash display field.
"""
password = ReadOnlyPasswordHashField()
class Meta:
model = User
fields = ('username', 'email', 'password', 'first_name', 'last_name', 'is_active', 'is_superuser')
# 修改creatersuperuser命令显示表单信息
class UserAdmin(BaseUserAdmin):
# The forms to add and change user instances
form = UserChangeForm
add_form = UserCreationForm
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ('username', 'email', 'first_name', 'last_name', 'is_active', 'is_superuser')
fieldsets = (
(None, {'fields': ('username', 'email', 'password')}),
('Personal info', {'fields': ('first_name', 'last_name')}),
('Permissions', {'fields': ('is_active', 'is_superuser')}),
)
# add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
# overrides get_fieldsets to use this attribute when creating a user.
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'email', 'first_name', 'last_name', 'password1', 'password2'),
}),
)
search_fields = ('email', 'username', 'first_name', 'last_name')
ordering = ('email', 'username', 'first_name', 'last_name')
filter_horizontal = ()
自定义app信息
# 根目录或app目录下均可
from django.apps import AppConfig
class BMSystemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' # 修改表自增为BigAuto,默认Auto
name = 'bm_system' # 设定app name
# 在setting中INSTALLED_APPS中添加配置替换原有信息
INSTALLED_APPS = [
'simpleui',
'django_filters',
'rest_framework',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bm_system.apps.BMSystemConfig',
'corsheaders',
]
修改login
def login(request, user, backend=None):
"""
Persist a user id and a backend in the request. This way a user doesn't
have to reauthenticate on every request. Note that data set during
the anonymous session is retained when the user logs in.
"""
session_auth_hash = ''
if user is None:
user = request.user
if hasattr(user, 'get_session_auth_hash'):
session_auth_hash = user.get_session_auth_hash()
# 查找现有请求中是否存在session信息,如果存在但信息和pk等信息不服则执行flush清除session,否则执行cycle_key新建或刷新session
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
# To avoid reusing another user's session, create a new, empty
# session if the existing session corresponds to a different
# authenticated user.
request.session.flush()
else:
# 在使用redis时调用此步骤将通过调用django/contrib/sessions/backends/base.py中cycle_key方法进行redis存储
request.session.cycle_key()
# TODO 修改缓存存储内容
# user_data = {'id': user.pk, 'username': user.username, 'email': user.email, 'is_active': user.is_active,
# 'is_staff': user.is_staff, 'is_superuser': user.is_superuser}
# request.session.cycle_key(user_data)
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
else:
if not isinstance(backend, str):
raise TypeError('backend must be a dotted import path string (got %r).' % backend)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user)
# django/contrib/sessions/backends/base.py
def cycle_key(self):
"""
Create a new session key, while retaining the current session data.
"""
data = self._session
key = self.session_key
# 此处因为setting中session设置使用cache,所以会调用django/contrib/sessions/backends/cache.py中create方法
self.create()
self._session_cache = data
if key:
self.delete(key)
# django/contrib/sessions/backends/cache.py
def create(self):
# Because a cache can fail silently (e.g. memcache), we don't know if
# we are failing to create a new session because of a key collision or
# because the cache is missing. So we try for a (large) number of times
# and then raise an exception. That's the risk you shoulder if using
# cache backing.
for i in range(10000):
# 设置session
self._session_key = self._get_new_session_key()
try:
# 调用存储保存至redis
self.save(must_create=True)
except CreateError:
continue
self.modified = True
return
raise RuntimeError(
"Unable to create a new session key. "
"It is likely that the cache is unavailable.")
def save(self, must_create=False):
if self.session_key is None:
return self.create()
if must_create:
func = self._cache.add
# elif self._cache.get(self.cache_key) is not None:
elif self._cache.get(self.session_key) is not None:
func = self._cache.set
else:
raise UpdateError
# 此处func就是调用django-redis中set或add方法进行存储session至redis中,原会在cache_key中添加cache_key_prefix前缀,未避免麻烦可以在cache_key中修改
# self.cache_key为传入redis key
# self._get_session(no_load=must_create)为传入redis value,非必要不建议修改此值,本身可通过此对象_auth_user_id进去user表中查找对应用户信息,若需修改最好使用内部调用然后追加或合并结果的方式,不然在admin管理页面使用时将无法使用
# self.get_expiry_age())为传入redis TTL
result = func(self.cache_key,
self._get_session(no_load=must_create),
self.get_expiry_age())
if must_create and not result:
raise CreateError
修改logout
def logout(request):
"""
Remove the authenticated user's ID from the request and flush their session
data.
"""
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
# TODO 直接发送登出接口时添加Authorization检测
# 为了使logout在前后端分离中也可使用,分别检测header authorization 及cookie
authorization = request.META.get('HTTP_AUTHORIZATION')
if authorization:
token = authorization
# 从Redis中取出token,前提是已经在settings中配置Redis
_cache = caches[settings.SESSION_CACHE_ALIAS]
user = _cache.get(token)
if user:
user = bm_system.models.User.objects.filter(pk=user['_auth_user_id'])
else:
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
request.session.flush()
else:
user = getattr(request, 'user', None)
if not getattr(user, 'is_authenticated', True):
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
request.session.flush()
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
修改session middleware
# django/contrib/sessions/middleware.py
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
return response
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty.
# 有cookie但是显示session为空则会删除cookie
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
patch_vary_headers(response, ('Cookie',))
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
# 每次session中间件均会检测刷新session,存储方式何在此处进行修改
request.session.save()
# TODO 修改缓存存储内容
# token = request.session.session_key
# _cache = caches[settings.SESSION_CACHE_ALIAS]
# user = _cache.get(token)
# request.session.save(user=user)
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
return response