强调:Django版本:1.9.5,Python版本:3.5
很多时候我们都要为一个系统开发用户验证的功能,而Django就为我们提供了一套现成的用户验证方法。
Django的用户模型在contrib.auth.models.py
,如下:
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'
查看AbstractUser
的源码,我们可以看到这个用户实体具有如下属性:
username、first_name、last_name、email、is_staff、is_active、date_joined
除此之外,AbstractUser
还继承AbstractBaseUser
和PermissionsMixin
。
AbstractBaseUser
的属性有:password、last_login
PermissionsMixin
用于权限控制。
当新建了一个Django项目,并且配置了数据库后,如果运行如下命令:
python manage.py migrate
就可以在数据库中看到生成了几张表,其中auth_user即为用户实体对应的表。
既然用户实体有了,那怎么进行用户验证呢。相关源码在django.contrib.auth.backends.ModelBackend
。
在ModelBackend
中,有一个叫authenticate
的方法,它就是验证逻辑的核心:
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)
可以看出,如果用户不存在,那么Django会为这个用户设置当前输入的密码。
这个方法的使用方式如下:
from django.contrib import auth
user = auth.authenticate(username='xxx', password='xxx')
if user is not None:
auth.login(request, user)
调用auth.login
的目的是为这个用户设置一些session信息,具体可参考源码。
当用户退出登录时,需要调用如下方法清除session:
auth.logout(request)
很多时候我们的用户实体的属性可能不仅仅是这些,又或者不仅仅验证用户名和密码是否正确。
这个时候我们就可以自定义用户模型和验证逻辑,自定义用户模型可以继承Django的AbstractBaseUser
。
自定义验证逻辑,除了可以继承ModelBackend
并重写authenticate
方法之外,也可以写一个普通类,但是其中至少包含authenticate
和get_user
两个方法。
当完成以上两个类后,就需要在配置文件settings.py
中引入它们:
AUTH_USER_MODEL = 'user.CustomUser'
AUTHENTICATION_BACKENDS = [
'user.views.MyBackend'
]
AUTHENTICATION_BACKENDS
配置项可以包含多个backend,Django将从上到下执行,一旦用户验证通过,将不再执行后面的验证方法。如果在这过程产生PermissionDenied
异常,验证也将立马终止。这个逻辑的相关源码在auth.authenticate
,如下:
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)
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
try:
user = backend.authenticate(**credentials)
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是否接受传进来的参数,如果不接受,将继续寻找下一个backend。