1-Django - auth组件
about
Django3.2 + Python3.9
Django通过内置auth模块实现了用户认证系统。
auth模块本身也是一个Django app,默认使用auth_user
表来存储用户数据,对应的模型类是User。
本篇就来学习一下这个auth app为我们提供了哪些便利的用户认证功能。
快速上手
在你的Django项目中,无需写任何模型类。
在执行完数据库迁移命令之后,创建一个超级管理员:
(base) D:\mydata\MyProject\about_auth>python manage.py makemigrations
(base) D:\mydata\MyProject\about_auth>python manage.py migrate
(base) D:\mydata\MyProject\about_auth>python manage.py createsuperuser
Username (leave blank to use 'zhangkai'): root
Email address: root@qq.com
Password: root1234
Password (again): root1234
Superuser created successfully.
这样就有了一个超级管理员root
密码root1234
,用这个超级用户来登录admin后台,你也可以用这个超级用户来使用auth app提供的各种认证功能。
auth_user表和User模型类
上一小节中,一顿操作创建出来一个超级用户,那么这个超级用户被保存到哪张表里了?这个表有哪些字段需要我们关注.....
首先auth_user表其本质上对应就是auth app下models.py中的User模型类。
User模型类内部没干什么,主要功能全部继承于AbstractUser类。
from django.contrib.auth.models import User # 这么导入
from django.contrib.auth.models import AnonymousUser # 还有这个匿名对象的模型类也需要注意
# 长这样
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
可以看到User这个模型类,基本上没干啥活,字段映射关系都有父类AbstractUser干了.....
首先来说auth_user表结构长这样:
create table auth_user
(
id integer not null
primary key autoincrement,
password varchar(128) not null,
last_login datetime,
is_superuser bool not null, -- 是否是超级用户
username varchar(150) not null
unique,
last_name varchar(150) not null, -- 英文 姓
email varchar(254) not null,
is_staff bool not null, -- True表示能登录django admin后台,False表示不能登录django admin后台
is_active bool not null, -- True表示该账户是激活的,可以在auth中进行登录认证,False表示未激活,可以在不删除用户的情况禁止登录
date_joined datetime not null,
first_name varchar(150) not null -- 英文 名
);
其次要了解其模型类的继承关系。
简单介绍下:
-
UserManger类,主要提供创建用户信息这些操作。
-
User类,就是auth app默认的模型类。
-
AbstractUser类,User类的父类,主要实现了各种字段映射和其它功能,如权限,看看源码就知道了,解释的很清楚。
-
AbstractBaseUser类,是AbstractUser类的父类,扩展了AbstractUser类的功能,比如user对象是否通过认证?是否是匿名用户?设置密码、校验密码.....
-
PermissionsMixin类,是AbstractUser类的父类,扩展了AbstractUser类的功能,比如user对象是否是超级用户。
-
AnonymousUser类,如果用户认证失败,或者未认证,那么
request.user
就是AnonymousUser,它是AnonymousUser类的实例化对象。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: if not ( session_hash and hasattr(user, '_legacy_get_session_auth_hash') and constant_time_compare(session_hash, user._legacy_get_session_auth_hash()) ): request.session.flush() user = None return user or AnonymousUser()
-
想要扩展auth_user表,我们就需要在自己的app的models.py中定义自己的模型类,来继承AbstractUser类即可。注意这只是做额外的扩展字段,比如添加一个手机号字段,但都可以使用auth app的各种功能。
-
如果想要进一步扩展,那么我们就在自己的app的models.py中重写AbstractUser方法,只要也继承AbstractBaseUser和PermissionsMixin,也照样可以使用auth app的各种功能。
auth app的认证组件
再来介绍一下auth app提供的各种认证功能组件。
完整的项目是示例代码。
链接:https://pan.baidu.com/s/1IBatFeW2iZeyltfj-QSw1w?pwd=6pus 提取码:6pus
login/authenticate
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
def login(request):
# 如果用户未登录,那么request.user是个匿名用户
# print(111111, request.user) # AnonymousUser
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
obj = auth.authenticate(request, username=user, password=pwd)
# 如果校验成功,返回user对象,否则返回None
# print(111, obj)
if obj:
next = request.GET.get('next')
# 登录成功之后,通过django auth内置的login将用户信息绑定到request对象上,另外也添加了session
auth_login(request, obj)
# 绑定成功后,关于这个用户的信息,可以通过request.user来获取
# 注意,登录失败的话,request.user是个匿名用户
# print(request.user)
# # 如,获取用户的各个字段
# print(request.user.username)
# print(request.user.email)
# print(request.user.is_superuser)
# print(request.user.is_active)
# print(request.user.is_staff)
# 添加了session信息
# print(request.session)
# print(request.session.__dict__)
if next:
return redirect(next)
else:
return redirect('/index/')
else:
return render(request, 'login.html', {"msg": "user or password error!"})
return render(request, 'login.html')
结合上面的views.py中的部分代码。
首先了解一个request.user
对象, 这个对象在登录成功之前,它指向的是一个匿名用户AnonymousUser
。
authenticate
在登录校验中,我们使用的是auth app提供的authenticate(request, username, password)
函数进行校验,如果校验成功,返回当前登录的成功的User模型类的对象,否则返回None。
login
登录成功之后,使用的是auth app提供的login(request, obj)
函数保存用户信息:
- 将User模型类的的对象obj绑定到
request.user
上,我们可以通过request.user
点出该模型类对象的各个字段,如用户名、email等。在用户登录成功之后,我们可以在别的视图函数中直接提取用户的相关信息,非常方便。 - 保存session信息。
另外,@login_required
装饰器的内部校验也依赖login方法。
logout
见名知意,logout(request)
是auth app提供的专门用来做退出的函数,它干了两件事,清空用户的session信息,将request.user
重置为AnonymousUser
。
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
@login_required
def logout(request):
# django auth内置的logout,用于退出登录状态
# 相当于flush session中的关于用户的登录信息
auth_logout(request)
# 退出之后,清空各种session信息和request.user
print(request.user) # AnonymousUser
print(request.session)
print(request.session.__dict__)
return redirect('/login/')
@login_required
auth app还提供了一个@login_required
装饰器,用于限制某些视图必须登录才能访问。
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
@login_required
def index(request):
users_obj = User.objects.all()
return render(request, 'index.html', {"users_obj": users_obj})
def login(request):
# 如果用户未登录,那么request.user是个匿名用户
# print(111111, request.user) # AnonymousUser
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
obj = auth.authenticate(request, username=user, password=pwd)
# 如果校验成功,返回user对象,否则返回None
# print(111, obj)
if obj:
next = request.GET.get('next')
# 登录成功之后,通过django auth内置的login将用户信息绑定到request对象上,另外也添加了session
auth_login(request, obj)
if next:
return redirect(next)
else:
return redirect('/index/')
else:
return render(request, 'login.html', {"msg": "user or password error!"})
return render(request, 'login.html')
@login_required
装饰器用法也很简单,加在对应的视图上就行了。
但是!还有需要注意的点,它内部会判断用户是否登录,如果发现未登录,坑来了,它会默认的跳转到/accounts/login/
这个路由进行登录,登录成功返回到被装饰的视图上(next=/index/
)。
http://127.0.0.1:8000/accounts/login/?next=/index/
但问题来了,我们自己定义的登录路由如果不是/accounts/login/
,就会404大黄页!!!所以,解决办法有两个,你把你的登录路由也改为/accounts/login/
;要么就是在settings.py中自定义路由:
# settings.py
# 这里配置成你项目登录页面的路由
# 如果这里不配置,那么你的的登录路由需要写成这样/accounts/login/
LOGIN_URL = '/login/'
# urls.py
urlpatterns = [
path('login/', views.login), # setting中配置了,可以自定义路由了
re_path(r'^accounts/login/', views.login), # settings中不配置,你写成这样的路由
]
is_authenticated
如果用户登录成功,那么request.user.is_authenticated
就等于True,可以用来干嘛么....就差不多跟上面的@login_required
装饰器类似,我这里简单列个示例:
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
@login_required
def index(request):
users_obj = User.objects.all()
return render(request, 'index.html', {"users_obj": users_obj})
def index2(request):
"""
这个视图就是为了演示is_authenticated的用法
"""
print('is_login', request.user.is_authenticated)
# 注意,根据django版本的不同,你要看下is_authenticated源码,是否需要加括号
# 源码在 from django.contrib.auth.base_user import AbstractBaseUser 类中
if request.user.is_authenticated: # 如果登录成功,就正常的执行视图函数的内容
users_obj = User.objects.all()
return render(request, 'index.html', {"users_obj": users_obj})
else: # 否则就跳转到登录页面进行登录认证
return redirect('{}?next={}'.format(settings.LOGIN_URL, request.path))
create/create_user/create_superuser
添加用户信息,可以根据需求来使用:
create
:使用create
方法就跟平常我们使用orm插入记录是一样的,但因为auth app的User表的密码字段是密文,所以创建用户时需要注意,User.objects.create()
,这样创建的用户密码是明文的,这样会导致你无法通过登录校验,所以需要单独处理。create_user
:直接通过该方法创建一个密文的用户。create_superuser
:创建一个具有超级管理员权限的用户,它需要三个必传参数,用户名、密码、邮箱。等价于python manage.py createsuperuser
创建的超级管理员用户。
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
def register(request):
form_obj = AuthModelForm()
if request.method == "POST":
form_obj = AuthModelForm(request.POST)
if form_obj.is_valid():
# print(form_obj.cleaned_data)
# {
# 'username': 'a', 'password': 'aa', 're_password': 'aa',
# 'email': 'aaa@qq.com',
# 'is_active': True, 'is_staff': False, 'is_superuser': False
# }
# 把无关的字段删除掉
form_obj.cleaned_data.pop('re_password')
# ------ 使用普通的create方法创建用户 ------
# user_obj = User.objects.create(**form_obj.cleaned_data)
# user_obj.set_password(form_obj.cleaned_data.get('password'))
# user_obj.save()
# ------ 使用create_user方法创建用户 ------
# 由于前端在注册时,是否具有某些权限已经勾选了,所以,这里直接使用create_user就行了,如果is_superuser的值是True,等同于create_superuser
User.objects.create_user(**form_obj.cleaned_data)
# ------ 使用create_user方法创建用户 ------
# 如果要创建超级用户,必须要传下面三个参数,其它字段的值都保持默认即可,这样创建出来的用户就是超级用户
#
# User.objects.create_superuser(
# username=form_obj.cleaned_data.get('username'),
# password=form_obj.cleaned_data.get('password'),
# email=form_obj.cleaned_data.get('email'),
# )
return redirect('/index/')
else:
return render(request, 'register.html', {"form_obj": form_obj, "title": "注册用户"})
return render(request, 'register.html', {"form_obj": form_obj, "title": "注册用户"})
set_password/check_password
set_password
、check_password
这两个auth app提供的方法专门用来处理密码相关的,比如用户修改密码,就要首先检查下原密码是否正确,然后再输入新的密码,并将密码进行更新到User表。
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
@login_required
def reset_password(request, pk):
form_obj = RestPasswordModelForm()
if request.method == "POST":
form_obj = RestPasswordModelForm(request.POST)
if form_obj.is_valid():
print("reset_password", form_obj.cleaned_data, pk)
obj = User.objects.filter(pk=pk).first()
# django auth内置的set_password,用于为指定用户设置一个密文密码
obj.set_password(raw_password=form_obj.cleaned_data.get("password"))
obj.save() # 别忘了save
return redirect('/index/')
else:
return render(request, 'register.html', {"form_obj": form_obj, "title": "重置密码"})
return render(request, 'register.html', {"form_obj": form_obj, "title": "重置密码"})
@login_required
def check_password(request):
if request.is_ajax():
old_pwd = request.POST.get("old_pwd")
pk = request.POST.get("pk")
obj = User.objects.filter(pk=pk).first()
data = {}
# django auth内置的check_password,用于检查指定用户输入的密码是否正确,正确返回True,否则返回False
print('check_password', obj.check_password(old_pwd))
if obj.check_password(old_pwd):
data['status'] = True
else:
data['status'] = False
return JsonResponse(data)
修改用户信息
这里额外的介绍下如何更新用户的信息。
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
@login_required
def edit(request, pk):
# 因为涉及到了可能会编辑所有用户的信息,这里呢就不能用request.user了,而是要重新获取下要编辑的用户对象,单独处理
obj = User.objects.filter(pk=pk).first()
form_obj = AuthModelForm(instance=obj)
if request.method == "POST":
form_obj = AuthModelForm(instance=obj, data=request.POST)
if form_obj.is_valid():
print(form_obj.cleaned_data)
form_obj.cleaned_data.pop('re_password')
form_obj.save()
# 上面直接保存的话,密码还是明文的,这样的话,这个用户密码就是非法了,以后无法通过校验了
# 所以要使用set_password单独处理下,这样明文密码最后保存到数据库就是密文的了
obj.set_password(form_obj.cleaned_data.get('password'))
obj.save() # 别忘了save
return redirect('/index/')
else:
render(request, 'register.html', {"form_obj": form_obj, "title": "编辑用户信息"})
return render(request, 'register.html', {"form_obj": form_obj, "title": "编辑用户信息"})
主要是密文密码要特殊处理下,其他的没啥。
注意,如果修改的是当前登录的用户自己的用户信息,修改完成后,就需要重新登录。
批量创建用户
也是额外的写个批量创建用户的示例,方便以后使用。
import random
import faker
from django.shortcuts import render, redirect, HttpResponse
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth import logout as auth_logout, login as auth_login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from app01.myForms import AuthModelForm, RestPasswordModelForm
from django.conf import settings
def bulk(request):
""" 批量创建用户 """
fk = faker.Faker(locale='zh_CN')
def bulk_create(num):
user_obj = User(
username=fk.name() + str(num), # 防止出现可能的重名
email=fk.email(),
is_active=random.choice([True, False]),
is_staff=random.choice([True, False]),
is_superuser=random.choice([True, False]),
)
user_obj.set_password('123') # 默认密码 123
user_obj.save()
total = 10
for i in range(total):
bulk_create(i)
return HttpResponse("已成功创建 {} 条用户信息".format(total))
扩展auth_user表
如果想在auth app默认的auth_user表现有字段的基础上,添加额外的字段,那么我们就需要手动的在我们自己的app/models.py中进行手动定义模型类,相当于重构auth app 默认User表。
注意,是在现有的字段上扩展添加新的字段。
必要的设置
咱们自己app中的models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser # 别忘了导入
class UserInfo(AbstractUser): # 必须继承这个AbstractUser类,并且这个模型类的类名不能叫做User,否则报错
# 搞一个字段意思下得了
phone = models.CharField(max_length=11, verbose_name='手机号', default='')
def __str__(self):
return self.username
settings.py
中要进行声明,不然数据库迁移的时候会报错。
# 我的项目app叫做app01
AUTH_USER_MODEL = 'app01.UserInfo'
如果遇到报错,就删除:
1. 删除自己app\migrations中的000开头的文件
2. 删除下面路径下的000开头的文件
\Lib\site-packages\django\contrib\admin\migrations
\Lib\site-packages\django\contrib\auth\migrations
然后再重新执行数据库迁移命令。
注意,想要扩展auth_user表,最好是在首次运行makemigrations和migrate之前进行,否则,经过实测,哪怕不报错,你自定义的模型类也不会被创建!!!那么对于已经执行了数据库迁移命令了怎么办?好办,删库之后,再重新进行数据库迁移,当然,注意数据备份
具体参见:https://docs.djangoproject.com/zh-hans/4.0/topics/auth/customizing/#extending-user
现在我们就不用在使用auth_user这个表了, 而是使用UserInfo模型类和对应的app01_userinfo表了。
而auth app提供的认证功能都照常使用。
如果你参考的是我的代码示例的话,就是将app01/myForm
和app01/views
中所有的原来的User替换为UserInfo即可,然后modelfrom中添加一个phone字段,用于添加用户信息,其他代码不变。
重写AbstractUser类
当你想要更自由定义字段,又想要使用auth app的功能,那么就在我们自己的app中的models.py中重写AbstractUser类吧。
必要的设置
咱们自己app中的models.py:
from django.db import models
from django.contrib import auth
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import AbstractUser, AbstractBaseUser, PermissionsMixin, UserManager # 别忘了导入
# A few helper functions for common logic between User and AnonymousUser.
def _user_get_permissions(user, obj, from_name):
permissions = set()
name = 'get_%s_permissions' % from_name
for backend in auth.get_backends():
if hasattr(backend, name):
permissions.update(getattr(backend, name)(user, obj))
return permissions
def _user_has_perm(user, perm, obj):
"""
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, 'has_perm'):
continue
try:
if backend.has_perm(user, perm, obj):
return True
except PermissionDenied:
return False
return False
def _user_has_module_perms(user, app_label):
"""
A backend can raise `PermissionDenied` to short-circuit permission checking.
"""
for backend in auth.get_backends():
if not hasattr(backend, 'has_module_perms'):
continue
try:
if backend.has_module_perms(user, app_label):
return True
except PermissionDenied:
return False
return False
class UserProfile(AbstractBaseUser, PermissionsMixin):
# 重写的部分可以参考AbstractUser,看看人家是咋写的
username = models.CharField(
max_length=150,
unique=True,
help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.',
)
phone = models.CharField(max_length=11, verbose_name='手机号', default='')
# 密码字段AbstractBaseUser中有定义,这里可以省略
# password = models.CharField(max_length=128, verbose_name='密码')
email = models.EmailField()
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=True)
# 照抄AbstractUser
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = '用户表'
verbose_name_plural = "用户表"
def get_full_name(self):
# The user is identified by their email address
return self.username
def get_short_name(self):
# The user is identified by their email address
return self.username
def __str__(self): # __unicode__ on Python 2
return self.username
def has_perm(self, perm, obj=None):
# "Does the user have a specific permission?"
# Simplest possible answer: Yes, always
if self.is_active and self.is_superuser:
return True
return _user_has_perm(self, perm, obj)
def has_perms(self, perm_list, obj=None):
# "Does the user have a specific permission?"
# Simplest possible answer: Yes, always
for perm in perm_list:
if not self.has_perm(perm, obj):
return False
return True
def has_module_perms(self, app_label):
# "Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
if self.is_active and self.is_superuser:
return True
return _user_has_module_perms(self, app_label)
# UserProfile.objects.create...这些操作的 objects 就是下面的 objects
objects = UserManager()
其主要还是翻着人家的源码进行魔改,对于新手来说,难度稍微大些。
settings.py
中要进行声明,不然数据库迁移的时候会报错。
# 我的项目app叫做app01
AUTH_USER_MODEL = 'app01.UserProfile'
如果遇到报错,就删除:
1. 删除自己app\migrations中的000开头的文件
2. 删除下面路径下的000开头的文件
\Lib\site-packages\django\contrib\admin\migrations
\Lib\site-packages\django\contrib\auth\migrations
然后再重新执行数据库迁移命令。
现在我们就不用在使用auth_user这个表了, 而是使用UserProfile模型类和对应的app01_userprofile表了。
而auth app提供的认证功能都照常使用。
如果你参考的是我的代码示例的话,就是将app01/myForm
和app01/views
中所有的原来的User替换为UserProfile即可,其他代码基本不变。
that's all, see also: