Django权限系统
1745 浏览 3 years, 11 months
1 基本用户 (django 实现)
版权声明: 转载请注明出处 http://www.codingsoho.com/基本用户 (django 实现)
模型定义
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username, password and email are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username, password and email are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, digits and '
'@/./+/-/_ only.'),
validators=[
validators.RegexValidator(r'^[\w.@+-]+$',
_('Enter a valid username. '
'This value may contain only letters, numbers '
'and @/./+/-/_ characters.'), 'invalid'),
],
error_messages={
'unique': _("A user with that username already exists."),
})
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
#
objects = UserManager()
#
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
#
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
#
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
#
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
#
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
上面是当前django的用户定义,User从AbstractUser继承而来。 abstract = True,所以AbstractUser是抽象类,不能实例化
swappable
class User(AbstractUser):
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
swappable is an "intentionally undocumented" feature which is currently under development / in-test. It's used to handle "I have a base abstract model which has some foreign-key relationships." Slightly more detail is available from Django's ticketing system and github. Because it's a "stealth alpha" feature, it's not guaranteed to work (for anything other than User), and understanding the detailed operation will likely require diving into source code. It works with AUTH_USER_MODEL
because the User model and swappable
flag were developed together, specifically for each other.
models.Manager
- UserModel._default_manager
user = UserModel._default_manager.get_by_natural_key(username)
def get_by_natural_key(self, username):
return self.get(**{self.model.USERNAME_FIELD: username})
**
解决了get后面名字不确定的问题,也是之前困扰voith的一个问题
- UserModel._default_manager
MyUserManager: autherwrapper.MyUser.objects
- UserModel._base_manager
Manager: autherwrapper.MyUser._base_manager
参考文档
- https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.models.AbstractBaseUser
用户授权 authenticate
当项目调用authenticate时,它会直接调用这个函数
django.contrib.auth.__init__
def authenticate(**credentials):
"""
If the given credentials are valid, return a User object.
"""
for backend, backend_path in _get_backends(return_tuples=True):
try:
inspect.getcallargs(backend.authenticate, **credentials) # 遍历backend
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
#
try:
user = backend.authenticate(**credentials) # 调用backend的authenticate函数
except PermissionDenied:
# This backend says to stop in our tracks - this user should not be allowed in at all.
return None
if user is None:
continue
# Annotate the user object with the path of the backend.
user.backend = backend_path
return user
#
# The credentials supplied are invalid to all backends, fire signal
user_login_failed.send(sender=__name__,
credentials=_clean_credentials(credentials))
- inspect.getcallargs(backend.authenticate, **credentials)
首先它会遍历backend,根据参数去找到合适的authenticate函数,这个backend在settings.py文件AUTHENTICATION_BACKENDS里定义。
- user = backend.authenticate(**credentials)
当authenticate函数找到之后,它就会接下来执行授权操作,并返回用户信息
backend
backend的实现可以参考下面的例子
django.contrib.auth.backends.py
class ModelBackend(object):
"""
Authenticates against settings.AUTH_USER_MODEL.
"""
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
如果函数调用authenticate(username, password)或者authenticate(email, password)(如果email是USERNAME_FIELD),那么该backend会被调用
参考文档
[1] [https://docs.djangoproject.com/en/dev/topics/auth/customizing/](https://docs.djangoproject.com/en/dev/topics/auth/customizing/)#writing-an-authentication-backend
demo代码里backend的authenticate的参数包含request,调用authenticate时,必须request=request,
否则django.contrib.auth.__init__的authenticate(**credentials)
函数在getcallargs这一步会出错,因为request这个参数不能传进**credentials
学到这儿的时候对Keyword Arguments理解还不够,刚好学习了一下,可以看一下下面文档
登陆 login
__init__.py (site-packages\django\contrib\auth)
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
HASH_SESSION_KEY = '_auth_user_hash'
REDIRECT_FIELD_NAME = 'next'
def login(request, user):
"""
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()
#
if SESSION_KEY in request.session:
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
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:
request.session.cycle_key()
# 正常用户登录时,会保存这个用户session,这样在response的消息里的request里面会带出去user信息
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = user.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)
- request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
SESSION_KEY会在后面_get_user_session_key中使用,用于获取用户 - request.session[BACKEND_SESSION_KEY] = user.backend
如果授权成功,django会把这个授权方式保存到session里,session周期里的下一次接入还是用这种方式。如果要强迫用不同方法授权,一个简单的方法是调用Session.objects.all().delete()
注意两个user的差别
{{user}}
{{request.user}}
```
这两个user分别是由下面的middleware传进来的
``` python
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
context_processors
django.contrib.auth.context_processors.auth
def auth(request):
"""
Returns context variables required by apps that use Django's authentication
system.
If there is no 'user' attribute in the request, uses AnonymousUser (from
django.contrib.auth).
"""
if hasattr(request, 'user'):
user = request.user
else:
from django.contrib.auth.models import AnonymousUser
user = AnonymousUser()
#
return {
'user': user,
'perms': PermWrapper(user),
}
user赋值在这儿完成,如果是非登陆用户,则为AnonymousUser
middleware
Middleware.py (site-packages\django\contrib\auth
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
class AuthenticationMiddleware(object):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE_CLASSES setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
)
request.user = SimpleLazyObject(lambda: get_user(request))
获取用户 get_user
系统里多个地方都调用了get_user
跟authenticate类似,它也会轮询多个backend找到对应的函数
django/contrib/auth/__init__
def get_user(request):
"""
Returns the user model instance associated with the given request session.
If no user is retrieved an instance of `AnonymousUser` is returned.
"""
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 ('django.contrib.auth.middleware.SessionAuthenticationMiddleware'
in settings.MIDDLEWARE_CLASSES and 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()
- (1) _get_user_session_key(request) SESSION_KEY在login中赋值了
django/contrib/auth/backends.py
class ModelBackend(object):
def get_user(self, user_id):
UserModel = get_user_model()
try:
return UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
django/contrib/auth/forms.py
class AuthenticationForm(forms.Form):
def get_user(self):
return self.user_cache
django/contrib/auth/middleware.py
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
应用调用
https://docs.djangoproject.com/en/dev/topics/auth/default/#how-to-log-a-user-in
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request= request, username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...