一 、auth模块
二 、User对象
三 、扩展默认的auth_user表
在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,真是个麻烦的事情。
Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统–auth,它默认使用 auth_user 表来存储用户数据,使用auth模块来进行用户认证,那么需要使用人家django自带的auth_user表来存储用户的信息数据。
导入模块:
from django.contrib import auth
可以通过python manage.py createsuperuser来给auth_user表添加数据,创建一个超级管理员:
python manage.py createsuperuser
数据库中,使用createsuperuser创建出来的用户密码会加密,这里下面会写到:
内置的User模型拥有以下的字段:
username: 用户名。150个字符以内。可以包含数字和英文字符,以及_、@、+、.和-字符。不能为空,且必须唯一!
first_name:歪果仁的first_name,在30个字符以内。可以为空。
last_name:歪果仁的last_name,在150个字符以内。可以为空。
email:邮箱。可以为空。
password:密码。经过哈希过后的密码。
#groups:分组。一个用户可以属于多个分组,一个分组可以拥有多个用户。groups这个字段是跟Group的一个多对多的关系。
#user_permissions:权限。一个用户可以拥有多个权限,一个权限可以被多个用户所有用。和 Permission属于一种多对多的关系。
is_staff:是否可以进入到admin的站点。代表是否是员工。这个字段如果不使用admin的话,可以自行忽略,不影响使用
is_active:是否是可用的。对于一些想要删除账号的数据,我们设置这个值为False就可以了,而不是真正的从数据库中删除。
is_superuser:是否是超级管理员。如果是超级管理员,那么拥有整个网站的所有权限。
last_login:上次登录的时间。
date_joined:账号创建的时间。
但是源码中可以看到AbstractUser是继承了AbstractBaseUser和PermissionsMixin:
那么AbstractUser和AbstractBaseUser到底有什么区别呢?
AbstractBaseUser中提供了一系列的关于密码校验以及邮箱校验的方法:
而在AbstractUser中则没有这些方法。
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self._password is not None:
password_validation.password_changed(self._password, self)
self._password = None
def get_username(self):
"""Return the username for this User."""
return getattr(self, self.USERNAME_FIELD)
def clean(self):
setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username()))
def natural_key(self):
return (self.get_username(),)
# 是否是匿名用户
@property
def is_anonymous(self):
"""
Always return False. This is a way of comparing User objects to
anonymous users.
"""
return False
# 身份认证
@property
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in templates.
"""
return True
# 通过createsuperuser创建出来的用户密码是用set_password进行哈希加密的
def set_password(self, raw_password):
self.password = make_password(raw_password)
self._password = raw_password
# 检查密码
def check_password(self, raw_password):
"""
Return a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
"""
def setter(raw_password):
self.set_password(raw_password)
# Password hash upgrades shouldn't be considered password changes.
self._password = None
self.save(update_fields=["password"])
return check_password(raw_password, self.password, setter)
def set_unusable_password(self):
# Set a value that will never be a valid hash
self.password = make_password(None)
def has_usable_password(self):
"""
Return False if set_unusable_password() has been called for this user.
"""
return is_password_usable(self.password)
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()
@classmethod
def get_email_field_name(cls):
try:
return cls.EMAIL_FIELD
except AttributeError:
return 'email'
@classmethod
def normalize_username(cls, username):
return unicodedata.normalize('NFKC', username) if isinstance(username, str) else username
AbstractBaseUser和AbstractUser总结:
AbstractUser是一个完整的用户模型,带有字段,作为抽象类,因此您可以从中继承并添加自己的配置
字段和方法。AbstractBaseUser仅包含身份验证功能,。如果您想重新考虑Django有关身份验证的一
些假设,那么AbstractBaseUser可以为您提供这样做的能力。
auth中提供了许多实用方法:
authenticate():
导入模块:from django.contrib.auth import authenticate
提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数,因为你仔细看看auth_user表的话,你会发现用户名和密码的字段名称就是username和password。
如果认证成功(用户名和密码正确有效,就是去auth_user表中查询一下是否存在这条记录),便会返回一个 User 对象,查询认证失败返回None。
authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
示例:
user = auth.authenticate(username='xx',password='123456')
用法:
def query(request):
if request.method == 'POST':
user = authenticate(request,**json.loads(request.body))
if not user:
return HttpResponse('用户名或者密码错误!')
else:
return HttpResponse('ok')
login(HttpRequest, user):
该函数接受一个HttpRequest对象,以及一个经过认证的User对象。
该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据,保持会话用。
def query(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
user_date = {
'username': username, 'password': password}
user_obj = authenticate(request, **user_date)
if not user_obj:
return HttpResponse('用户名或者密码错误!')
else:
可以简单理解为request.session['user_id']=user_id,并且将user_obj封装到了
request里面,通过request.user= user_obj
login(request, user_obj)
return redirect('index')
def index(request):
return render(request, 'index.html')
保存一个sessionid:
这里从AuthenticationMiddleware这个中间件点击进去的确可以看到是封装了一个reques.user对象,后续我们可以通过reques.user这个对象来获取user模型中的所有属性,具体源码后续是怎么实现的,有兴趣的可以自己看一下。
如果没有登录的情况下使用request.user呢?那么将返回一个AnonymousUser:
def index(request):
print(request.user) # 显示的是一个匿名用户
print(request.user.id)
print(request.user.username)
print(request.user.is_active)
return render(request, 'index.html')
结果:
登录后的:
注意的点:
只要使用login(request, user_obj)之后,request.user就能拿到当前登录的用户对象。否则request.user得到的是一个匿名用户对象(AnonymousUser Object,是request.user的默认值)。
request.user一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser
的一个实例。你可以通过 is_authenticated() 区分它们。
例如:
user = authenticate(request, **request.json)
if not user:
return api.bad_request()
auth_login(request, user)
return api.ok(user, group='userinfo')
另外注意:
user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。
get_user源码中返回的是user或者是AnonymousUser()实例,也就是说user为真则返回user对象,反之返回一个AnonymousUser的实例:
AnonymousUser,匿名用户所有字段都为假,表示无任何权限:
id 永远为None。
username 永远为空字符串。
get_username() 永远返回空字符串。
is_staff 和 is_superuser 永远为False。
is_active 永远为 False。
groups 和 user_permissions 永远为空。
is_anonymous() 返回True 而不是False。
is_authenticated() 返回False 而不是True。
set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
在真实项目中如果没有登录但操作request.user则会触发AnonymousUser异常,根据操作不同异常可能会不相同:
logout(request):
该函数接受一个HttpRequest对象,无返回值。
当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
示例:
def api_logout(request):
logout(request)
return redirect('login')
Django使用session和中间件关联请求对象中和认证系统。
认证用户:
每一次请求中都包含一个request.user属性,表示当前用户。如果该用户未登陆,该属性的值是一个AnonymousUser实例(匿名用户),如果已经登录,该属性就是一个User模型的实例。
注意:
通过认证并不意味着用户拥有任何权限,甚至也不检查该用户是否处于激活状态,这只是表明用户成功的通过了认证。 这个方法很重要, 在后台用request.user.is_authenticated()判断用户是否已经登录,如果true则可以向前台展示request.user.name。
可以使用is_authenticated方法进行判断,如下:
def admin_required(viewfunc):
@wraps(viewfunc)
def wrapper(request, *args, **kwargs):
# 是否登录
if not request.user.is_authenticated:
return api.not_authorized()
# 是否是管理员
if not request.user.is_admin:
return api.forbidden(message='需要管理员用户')
return viewfunc(request, *args, **kwargs)
return wrapper
后续直接将该装饰器放在视图函数中即可,使用方法如下:
from django.utils.decorators import method_decorator
@method_decorator(admin_required, name='dispatch')
class UserView(View):
def get(self, request, id=None):
if id is not None:
return self._get_user(request, id)
else:
return self._list_user(request)
示例,可以在index页面判断用户是否登录:
方法一:
def index(request):
if not request.user.is_authenticated:
return redirect('login')
else:
return render(request, 'index.html')
方法二:
login_requierd():
django提供了用于此种情况的装饰器:login_requierd()。
首先在settings.py文件中设置如下内容:
LOGIN_URL = '/login/' # 需要重定向到的路径,一定是存在的
视图函数:
@login_required()
def index(request):
if not request.user.is_authenticated:
return redirect('login')
else:
return render(request, 'index.html')
这里url路径前面表示我们自定义的重定向路径,后面表示登录成功后要进入的页面:
这里需要注意is_authenticated和authenticated的区别:
authenticated:authenticate 就是身份验证,举个例子,比如你提供了账号、密码,
传给 authenticate 他就会检查你提供的信息是否正确,正确了就会返回 user。
is_authenticated:验证用户是否登录,在每个Web请求中都提供一个 request.user
属性来表示当前用户。如果当前用户未登录,则该属性为AnonymousUser的一个实例,
反之,则是一个User实例。
create_user():
封装在一个UserManager管理器中,用于创建用户:
示例:
def create(request):
User.objects.create_user(username='张三',password='123')
return HttpResponse('ok')
create_superuser():
auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。
def create(request):
User.objects.create_superuser(username='李四',password='456')
return HttpResponse('ok')
check_password(raw_password):
检查密码是否正确的方法,需要提供当前请求用户的密码。密码正确返回True,否则返回False。
可直接打印:
request.user.check_password(password)
set_password(raw_password):
修改密码的方法,接收 要设置的新密码 作为参数。
注意:设置完一定要调用用户对象的save方法!!!
request.user.set_password('新密码')
request.user.save()
request.user能够拿到认证所用用户表的数据属性,比如username, password等。
auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊,如果我要添加几个额外的字段怎么办?
答案是当然有了。
可以通过继承内置的 AbstractUser 或者AbstractBaseUser类(两者区别上述已经介绍),来定义一个自己的Model类。如果没有继承AbstractUser,那么makemigrateions和migrate的时候则会创建auth_user这张表,如果继承了AbstractUser则只会创建自己的user表,继承AbstractBaseUser则会创建auth_user表(这里有待商榷,本人未去尝试,但在我做的项目中是有这个auth_user表的)。
这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。继承表的好处是我们可以增加一些自己需要的字段,并且同时可以使用auth模块提供的接口、方法。
示例:
from django.contrib.auth.models import AbstractBaseUser,AbstractUser
class User(AbstractUser):
phone = models.CharField(max_length=11, null=True, unique=True)
注意:
User表里就不需要有auth_user里重复的字段了,比如说username以及password等,但是还是可以直接使用这些字段的,并且django会自动将password进行加密。
扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的User表来做用户认证。写法如下:
AUTH_USER_MODEL = "manytable.User"
创建完成之后不要忘了makemigrations和migrate。
后续就可以像使用默认的auth_user表那样使用我们的User表了。比如:
User.objects.create_superuser(username='李四',password='456')
User.objects.create_user(username='李四',password='456')
如有异议或者不明白不理解的欢迎加群交流:934244622