十五 Django的Auth认证系统
Django除了有强大的Admin管理系统之外,还提供了完善的用户管理系统.整个系统分为三大部分:用户信息,用户权限和用户组,在数据库中分别对应数据表auth_user,auth_permission和auth_group.
一 内置User实现用户管理
在创建Django项目时,Django已默认使用内置用户管理系统,在settings.py的INSTALLED_APPS.MIDDLEWARE和AUTH_PASSWORD_VALIDATORS中可以看到相关的配置新消息.
新创建一个App,命名为user,并在settings.py和url.py中配置APP的信息
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', 'app02.apps.App02Config', 'user' ]
urlpatterns = [ path('user/',include('user.urls')), ]
在App中分别添加urls.py和user.html文件
from django.urls import path from . import views urlpatterns = [ path('login.html',views.loginView,name='login'), path('register.html',views.registerView,name='register'), path('setpassword.html',views.setpasswordView,name='setpassword'), path('logout.html',views.logoutView,name='logout'), ]
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>{{ title }}</title> <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css"> </head> <body> <div class="flex-center"> <div class="container"> <div class="flex-center"> <div class="unit-1-2 unit-1-on-mobile"> <h1>MyDjango Auth</h1> {% if tips %} <div>{{ tips }}</div> {% endif %} <form class="form" action="" method="post"> {% csrf_token %} <div>用户名:<input type="text" name='username'></div> <div>密 码:<input type="password" name='password'></div> {% if new_password %} <div>新密码:<input type="password" name='new_password'></div> {% endif %} <button type="submit" class="btn btn-primary btn-block">确定</button> </form> <div class="flex-left top-gap text-small"> <div class="unit-2-3"> <a href="{{ unit_2 }}">{{ unit_2_name }}</a> </div> <div class="unit-1-3 flex-right"> <a href="{{ unit_1 }}">{{ unit_1_name }}</a> </div> </div> </div> </div> </div> </div> </body> </html>
from django.shortcuts import render,HttpResponse from django.contrib.auth.models import User from django.contrib.auth import login,logout,authenticate def loginView(request): #设置标题和另外两个url链接 title = '登录' unit_2 = '/user/register.html' unit_2_name = '立即注册' unit_1 = '/user/setpassword.html' unit_1_name = '修改密码' if request.method == "POST": #获取username文本框的值,若无置为空 username = request.POST.get('username','') password = request.POST.get('password','') if User.objects.filter(username=username): #使用 authenticate() 函数。它接受两个参数,用户名 username 和 密码 password , # 并在密码对给出的用户名合法的情况下返回一个 User 对象。 如果密码不合法,authenticate()返回None user = authenticate(username=username,password=password) if user: #判断用户是否是激活态 if user.is_active: login(request,user) return HttpResponse('登录成功') else: print('%s用户未激活'%username) return render(request, "user.html", {"tips":'%s用户未激活'%username}) else: tips = '账号密码错误,请重新输入' else: tips = '用户不存在,请注册' return render(request,'user.html',locals())
注意: ①user.is_active:判断用户是否是激活态,可在admin后台系统查看

②f user.is_active:段代码任何情况下都不会被执行,需要在settings.py中进行配置,如下代码;
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
在整个登录过程中,并没有对User进行定义,而函数中使用的模型User来自于Django的内置模型,在数据库中对应的表为auth_user,字段说明如下:
| 字段 | 说明 |
| Id | int类型,数据表主键 |
| Password | varchar类型,代表用户密码,在默认情况下使用pbkdf2_sha256方式来存储和管理用户的密码 |
| last_login | datetime类型,最近一次登录的时间 |
| is_superuser | tinyint类型,表示该用户是否拥有所有的权限,即是否为超级用户 |
| Username | varchar类型,代表用户账号 |
| first_name | varchar类型,代表用户的名字 |
| last_name | varchar类型,代表用户的姓氏 |
| varchar类型,代表用户的邮件 | |
| is_staff | 用来判断用户是否可以登录进入Admin系统 |
| is_active | tinyint类型,用来判断该用户的状态是否被激活 |
| date_joined | datetime类型,账号的创建时间 |
用户注册功能实现
def registerView(request): # 设置标题和另外两个url链接 title = '注册' unit_2 = '/user/login.html' unit_2_name = '立即登录' unit_1 = '/user/setpassword.html' unit_1_name = '修改密码' if request.method == "POST": username = request.POST.get('username', '') password = request.POST.get('password', '') if User.objects.filter(username=username): tips= '用户已存在' else: #create_user用于创建并保存一个is_active=True的User对象.其中username不能为空,否则抛出ValueError异常; #而模型User的其他字段可作为函数create_user的可选参数,如email,first_name,password等若使用过程中没有设置函数参数password, #则User对象的set_unusable_password()函数将会调用,为当前用户创建一个随机密码 user = User.objects.create_user(username=username,password=password) user.save() tips = '注册成功,请登录' return render(request,'user.html',locals())
修改密码功能实现
from django.contrib.auth.hashers import make_password def setpasswordView(request): # 设置标题和另外两个url链接 title = '修改密码' unit_2 = '/user/login.html' unit_2_name = '立即登录' unit_1 = '/user/register.html' unit_1_name = '立即注册' new_password =True if request.method == "POST": username = request.POST.get('username','') old_password = request.POST.get('password','') new_password = request.POST.get('new_password','') if User.objects.filter(username=username): user = authenticate(username=username,password=old_password) if user: user.set_password(new_password) #函数set_password时在内置函数make_password的基础上进行封装而来的 #django默认情况下,使用pbkdf2_sha256方式来存储和管理用户密码,而内置函数make_password主要实现用户密码的加密功能 dj_ps = make_password(new_password,None,'pbkdf2_sha256') user.password = dj_ps user.save() tips = '密码修改成功' else: tips = '用户密码错误' else: tips = '用户不存在' return render(request,'user.html',locals()) def logoutView(request): logout(request) return HttpResponse('用户已退出')
Django中内置函数check_password,该函数是对加密前的密码与加密后的密码进行验证匹配,判断两者是否为同一密码
用户注销功能实现
def logoutView(request): logout(request) return HttpResponse('用户已退出')
二 发送邮件实现密码找回
#邮箱配置信息,设置django与邮件服务器的连接方式为SSL EMAIL_USE_SSL = True #邮箱服务器,设置服务器的地址,该配置使用SMTP服务器 EMAIL_HOST = 'smtp.qq.com' #邮箱服务器端口,若使用SMTP服务器,则端口应为465或587 EMAIL_PORT = 465 #发送邮件的账号,该账号必须开启POP3/SMTP服务 EMAIL_HOST_USER = '414630491@qq.com' #SMTP服务密码 EMAIL_HOST_PASSWORD = 'jhxnwaegphldbjhg' #设置默认发送邮件的账号 DEFAULT_FROM_EMAIL= EMAIL_HOST_USER
urlpatterns = [ path('findPassword.html',views.findPassword,name='findPassword') }
from django.contrib.auth.hashers import make_password def setpasswordView(request): # 设置标题和另外两个url链接 title = '修改密码' unit_2 = '/user/login.html' unit_2_name = '立即登录' unit_1 = '/user/register.html' unit_1_name = '立即注册' new_password =True if request.method == "POST": username = request.POST.get('username','') old_password = request.POST.get('password','') new_password = request.POST.get('new_password','') if User.objects.filter(username=username): user = authenticate(username=username,password=old_password) if user: user.set_password(new_password) #函数set_password时在内置函数make_password的基础上进行封装而来的 #django默认情况下,使用pbkdf2_sha256方式来存储和管理用户密码,而内置函数make_password主要实现用户密码的加密功能 dj_ps = make_password(new_password,None,'pbkdf2_sha256') user.password = dj_ps user.save() tips = '密码修改成功' else: tips = '用户密码错误' else: tips = '用户不存在' return render(request,'user.html',locals()) def logoutView(request): logout(request) return HttpResponse('用户已退出') #找回密码 def findPassword(request): button = '获取验证码' if request.method == 'POST': username = request.POST.get('username','root') VerificationCode = request.POST.get('VerificationCode') password = request.POST.get('password') user = User.objects.filter(username=username) #用户不存在 if not user: tips = '用户' + username + '不存在' else: #判断验证码是否发送成功 if not request.session.get('VerificationCode'): #发送验证码并将验证码写进session中 button = '重置密码' tips = '验证码已发送' new_password = True VerificationCode = str(random.randint(1000,9999)) request.session['VerificationCode'] = VerificationCode user[0].email_user('找回密码',VerificationCode) #匹配验证码是否正确 elif VerificationCode == request.session.get('VerificationCode'): #密码加密处理并保存到数据库 dj_ps = make_password(password,None,'pbkdf2_sha256') user[0].password = dj_ps user[0].save() del request.session['VerificationCode'] #输入密码错误 else: tips = '验证码错误,请重新获取' new_password = False del request.session['VerificationCode'] return render(request,'user.html',locals())
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>找回密码</title> <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css"> </head> <body> <div class="flex-center"> <div class="container"> <div class="flex-center"> <div class="unit-1-2 unit-1-on-mobile"> <h1>MyDjango Auth</h1> {% if tips %} <div>{{ tips }}</div> {% endif %} <form class="form" action="{% url 'findPassword' %}" method="post"> {% csrf_token %} <div>用户名:<input type="text" name='username' value="{{ username }}"></div> <div>验证码:<input type="text" name='VerificationCode'></div> {% if new_password %} <div>新密码:<input type="password" name='password'></div> {% endif %} <button type="submit" class="btn btn-primary btn-block">{{ button }}</button> </form> </div> </div> </div> </div> </body> </html>
使用Django发送邮件的方法
①使用send_mail每次发送邮件都会建立一个新的链接,若发送多封邮件,就需要建立多个连接.
②send_mass_mail是建立单个连接发送多封邮件,所以一次性发送多封邮件时send_mass_mail要优于send_mail
③EmailMultiAlternUserative比前面两者更为个性化,可以设置邮件正文内容为HTML格式,也可以在邮件上添加附件,满足多方面开发需求
三 扩展User模型
在开发过程中,模型user可能满足不了复杂的开发需求,为了满足各种需求,Django提供了4种模型扩展的方法
代理模型:这是一种模型继承,这种模型在数据库中无须创建新数据表.一般用于改变现有模型的行为方式,如增加新方法函数等,并且不影响现有数据库的结构.当不需要在数据库中存储额外信息,而需要增加操作方法或更改模型的查询管理方式时,适合使用代理来扩展现有User模型.
Profile扩展模型User:当存储的信息与模型相关,而且并不改变模型User原有的认证方法时,可定义新的模型MyUser,并设置某个字段为OneToOneField,这样能与模型User形成一对一关联,该方法称为用户配置(User Profile).
AbstractBaseUser扩展模型User:当模型User的内置方法并不符合开发需求时,可使用该方法对模型User重新自定义设计,该方法对模型User和数据库架构影响很大.
AbstractUser扩展模型User:如果模型User内置的方法符合开发需求,在不改变这些函数方法的情况下,添加模型User的额外字段,可通过AbstractUser方式实现,使用AbstractUser定义的模型会替换原有模型User.
一般情况下,建议使用AbstractUser扩展模型User.因为该方法对原有模型影响较小且无须额外创建数据表,下面示例使用AbstractUser扩展模型User.(建议删除该数据库中全部的数据表)
步骤一
模型MyUser继承自AbstractUser类,AbstractUser类类已有内置模型User的字段属性,因此模型MyUser具有模型User的全部属性.
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class MyUser(AbstractUser): qq = models.CharField('QQ号码',max_length=16) weChat = models.CharField('微信账号',max_length=100) mobile = models.CharField('手机号码',max_length=11) #设置返回值 def __str__(self): return self.username
步骤二
在项目的settings.py中配置相关信息,如下所示,配置信息是将内置模型User替换成user定义的模型MyUser,若没有设置配置信息,在创建数据表的时候,会分别创建数据表auth_user和user_myuser.
AUTH_USER_MODEL = 'user.MyUser'
步骤三
迁移数据库
python manage.py makemigrations
python manage.py migrate
总结:使用AbstractUser扩展模型User,实现过程可分为两个步骤:
①定义新的模型MyUser,该模型必须继承AbstractUser类,在模型MyUser下定义的字段为扩展字段.
②在项目的配置文件settings.py中配置AUTH_USER_MODEL信息,在数据迁移时,将内置模型User替换成user定义的模型MyUser.
将模型MyUser展现在admin后台管理系统:
步骤一: 创建超级用户
python manage.py createsuperuser
步骤二: 创建MyUserAdmin
from django.contrib import admin from .models import MyUser from django.contrib.auth.admin import UserAdmin from django.utils.translation import gettext, gettext_lazy as _ # Register your models here. @admin.register(MyUser) class MyUserAdmin(UserAdmin): list_display = ['username','email','mobile','qq','weChat'] #修改用户时,在个人信息里添加'mobile','qq','weChat'的信息录入 #将源码的UserAdmin。fieldsets转换成列表格式 fieldsets = list(UserAdmin.fieldsets) #重写UserAdmin的fieldsets,添加'modile','qq','weChat'信息录入 fieldsets[1] = (_('Personal info'), {'fields': ('first_name', 'last_name', 'email', 'mobile', 'qq', 'weChat')})
步骤三 :修改app(即user)在后台的显示名称
from django.apps import AppConfig #设置APP(user)的用户名 import os #修改APP在Admin后台显示的名称 #default_app_config的值来自apps.py的类名 default_app_config = 'user.IndexConfig' # # #获取当前APP的命令 def get_current_app_name(_file): return os.path.split(os.path.dirname(_file))[-1] # # #重写类App01Config class IndexConfig(AppConfig): name = get_current_app_name(__file__) verbose_name = '用户管理'
继承内置模型User所定义的表单类
内置表单类form.py定义了多个内置表单类,如下表所示:
| 表单类 | 表单字段 | 说明 |
| UserCreationForm | username,password1,password2 | 创建新的用户信息 |
| UserChangeForm | password,模型User全部字段 | 修改已有的用户信息 |
| AuthenticationForm | username,password | 用户登录时所触发的认证功能 |
| PasswordRestForm | 将重置密码通过发送邮件方式实现密码找回 | |
| SetPasswordForm | password1,password2 | 修改或新增用户密码,设置密码时,无须对旧密码进行验证 |
| PasswordChangeForm | old_password,new_password1,new_password2 | 继承SetPasswrodForm修改密码前需要对旧密码进行验证 |
| AdminPasswordChangeForm | password1,password2 | 用于Admin后台修改用户密码 |
示例(以UserCreationForm实现用户注册功能)
from django.contrib.auth.forms import UserCreationForm from .models import MyUser class MyUserCreationForm(UserCreationForm): class Meta(UserCreationForm.Meta): model = MyUser #在注册界面添加邮箱,手机号码,微信号码和QQ号码 fields = UserCreationForm.Meta.fields + ('email','mobile','weChat','qq')
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>用户注册</title> <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css"> </head> <body> <div class="flex-center"> <div class="container"> <div class="flex-center"> <div class="unit-1-2 unit-1-on-mobile"> <h1>MyDjango Auth</h1> {% if tips %} <div>{{ tips }}</div> {% endif %} <form class="form" action="" method="post"> {% csrf_token %} <div>用户名:{{ user.username }}</div> <div>邮 箱:{{ user.email }}</div> <div>手机号:{{ user.mobile }}</div> <div>Q Q 号:{{ user.qq }}</div> <div>微信号:{{ user.weChat }}</div> <div>密 码:{{ user.password1 }}</div> <div>密码确认:{{ user.password2 }}</div> <button type="submit" class="btn btn-primary btn-block">注 册</button> </form> </div> </div> </div> </div> </body> </html>
from django.shortcuts import render from .form import MyUserCreationForm # 使用表单实现用户注册 def registerView(request): if request.method == 'POST': user = MyUserCreationForm(request.POST) if user.is_valid(): user.save() tips = '注册成功' user = MyUserCreationForm() else: user = MyUserCreationForm() return render(request, 'user.html',locals())
四 设置用户权限
用户权限主要是对不同的用户设置不同的功能使用权限,而每个功能主要以模型划分

以user|用户|Can add user
user代表项目的APP
用户代表APP所定义的模型MyUser
Can add user代表该权限可对模型MyUser执行新增操作
一般情况下,在执行数据迁移时,每个模型默认拥有增(add),改(change),删(delete)权限。数据迁移完成后,可以在数据库中查看auth_permission的数据信息.
设置用户权限实战上对数据表user_myuser和auth_permission之间的数据设置多对多关系.首先需要了解用户,用户权限和用户组三者之间的关系,分别对应数据表user_myuser,auth_permission和auth_group.说明如下:
数据表user_myuser_user_permission:管理数据表user_myuser和auth_permission之间的多对多关系,实现用户权限设置
数据表user_myuser_groups:管理数据表user_myuser和auth_group之间的多对多关系,实现在用户组设置用户.
数据表auth_group_permissions:管理数据表auth_group和auth_permission之间的多对多关系,实现用户组设置权限
五 自定义用户权限
可以对某个模型设置特殊权限,比如设置访问权限.为了解决这种情况,在定义模型的时候,可以在模型的Meta中设置自定义权限(需要在数据库中清楚原有的数据表)
# 创建产品信息表 # 设置字段中文名,用于admin后台显示 class Product(models.Model): id = models.AutoField('序号', primary_key=True) name = models.CharField('名称',max_length=50) weight = models.CharField('重量',max_length=20) size = models.CharField('尺寸',max_length=20) type = models.ForeignKey(Type, on_delete=models.CASCADE,verbose_name='产品类型') # 设置返回值 def __str__(self): return self.name class Meta: permissions = ( ('visit_Product', 'Can visit Product'), )
然后迁移数据库,并查看auth_permission表

六 设置网页的访问权限
首先配置用户的流程
from django.urls import path from . import views urlpatterns = [ # path('findPassword.html',views.findPassword,name='findPassword'), path('login.html',views.loginView,name='login'), path('register.html',views.registerView,name='register'), # path('setpassword.html',views.setpasswordView,name='setpassword'), path('logout.html',views.logoutView,name='logout'), ]
from django.shortcuts import render,HttpResponse,redirect from django.contrib.auth.models import User,Permission from django.contrib.auth import login,logout,authenticate import random from django.contrib.auth.hashers import make_password from user.models import MyUser #用户登录 def loginView(request): tips = '请登录' title = '用户登录' if request.method == 'POST': username = request.POST.get('username','') password = request.POST.get('password','') if MyUser.objects.filter(username=username): user = authenticate(username=username,password=password) if user: if user.is_active: #登录当前用户 login(request,user) return redirect('/') else: tips = '账号密码错误,请重新输入' else: tips = '用户不存在,请注册' return render(request,'user.html',locals()) #用户注册 def registerView(request): title = '用户注册' if request.method == 'POST': username = request.POST.get('username','') password = request.POST.get('password','') if MyUser.objects.filter(username=username): tips = '用户已注册' else: user = MyUser.objects.create_user(username=username,password=password) user.save() #添加权限 permission = Permission.objects.filter(codename='visit_Product')[0] user.user_permissions.add(permission) return redirect('/user/login.html') return render(request,'user.html',locals()) #退出登录 def logoutView(request): logout(request) return redirect('/')
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>{{ title }}</title> <link rel="stylesheet" href="https://unpkg.com/mobi.css/dist/mobi.min.css"> </head> <body> <div class="flex-center"> <div class="container"> <div class="flex-center"> <div class="unit-1-2 unit-1-on-mobile"> <h1>MyDjango Auth</h1> {% if tips %} <div>{{ tips }}</div> {% endif %} <form class="form" action="" method="post"> {% csrf_token %} <div>用户名:<input type="text" name='username'></div> <div>密 码:<input type="password" name='password'></div> <button type="submit" class="btn btn-primary btn-block">确定</button> </form> </div> </div> </div> </div> </body> </html>
然后配置app index的流程
from django.conf.urls import url #在django2.0中同样可以导入1.0中的url from django.conf.urls import include from django.urls import path,register_converter from index import views urlpatterns = [ #首页 path('',views.index), path('user/',include('user.urls')), ]
from django.shortcuts import render from django.contrib.auth.decorators import login_required,permission_required # Create your views here. #使用login_required和permission_required分别对用户登录验证和用户权限验证 @login_required(login_url='/user/login.html') @permission_required(perm='index.visit_Product',login_url='/user/login.html') def index(requset): return render(requset,'index1.html',locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> <header id="top"> <!-- 内容显示区域 :width : 1211px --> <div id="top_box"> <ul class="lf"> <li><a href="#">华为官网</a></li> <li><a href="#">华为荣耀</a></li> </ul> <ul class="rt"> {#在模版中使用user变量是一个User或者AnoymousUser对象,该对象由模型MyUser实例化#} {% if user.is_authenticated %} <li>用户名: {{ user.username }}</li> <li><a href="/user/logout.html">退出登录</a></li> {% endif %} {#在模版中使用perms变量是Permission对象,该对象由模型Permission实例化#} {% if perms.index.add_product %} <li>添加产品信息</li> {% endif %} </ul> </div> </header> </head> <body> </body> </html>
视图函数中使用了装饰器login_required和permission_required,分别对当前用户的登录状态和用户权限进行了校验,说明如下:
login_required:设置用户登录访问权限,若当前用户尚未在登录界面完成登录而直接访问首页,程序自动跳转到登录页面,只有用户完成登录后才能访问首页.login_required的参数有redirect_field_name和login_url
参数redirect_field_name:默认值是next.当登录成功后,程序会自动跳回之前浏览的网页
参数login_required:设置登录界面的URL地址.默认值是settings.py的属性LOGIN_URL,而属性LOGIN_URL需要开发者自行在settings.py中配置
permission_required:验证当前用户是否拥有相应的权限,若没有用户使用权限,程序会自动跳转到登录页面或抛出异常.参数介绍如下:
参数perm:必须参数,判断当前用户是否拥有权限.参数值是固定格式,如index.visit_Product,index为项目名(app名),visit_product来自于数据表auth_permission的字段codename
参数login_url:设置登录界面的url地址,默认值为None,若不设置该参数,验证失败会抛出404异常
参数raise_exception: 设置抛出异常,默认值为False
在模板index.html中分别使用变量user和perms,但从视图函数index中可以发现,视图函数并没有将变量user和perms传递给模板.其实变量user和perms是由Django自动生成的,变量生成与配置文件settings.py的TEMPLATES设置有关.
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
因为TEMPLATES中定义了处理器集合context_processors,所以在解析模板Templates之前,Django首先依次运行处理器集合的程序.当运行到django.contrib.auth.context_processors.auth时,程序会变成变量user和perms,并且将变量传入模板变量TemplateContext中,所以在模板中可以直接使用变量user和perm.
七 设置用户组
用户组权限就是对用户进行分组管理,其作用是在权限控制中可以批量地对用户的权限进行分配.设置用户组权限主要是对数据表auth_group和auth_permission构建多对多的数据关系.
浙公网安备 33010602011771号