用户系统是现代网站的重要组成部分,对用户进行分组权限管理是非常必要的。
Django内置了一套用户和身份验证系统,不用太多代码开发就可以使用这个系统。
Django 的身份验证系统包括:
• 用户
• 权限:二元(是或否)旗标,指明用户是否能执行特定的任务
• 分组:把标注和权限赋予多个用户的通用方式
• 可配置的密码哈希系统
• 管理身份验证和权限核准的表单
• 登录用户或限制内容的视图工具
• 可更换的后端系统
Django 的身份验证系统十分通用,没有提供 Web 身份验证系统中某些常用的功能。
某些常用功能通过第三 方包实现:
• 密码强度检查
• 登录尝试次数限制
• 通过第三方验证身份(如 OAuth)
1、User对象
User 对象是这个身份验证系统的核心,通常用于标识与网站交互的人,还用于限制访问、记录用户资料,以 及把内容与创建人关联起来,等等。
在 Django 的身份验证框架中,只有一个用户类存在,因此 superusers 或管理后台的 staff 用户只是设定了特殊属性的用户对象,而不是分属不同类的用户对象。
默认用户主要有 下面几个属性:
• username
• password
• first_name
• last_name
1.1 创建超级用户
超级用户使用 createsuperuser 命令创建:
python manage.py createsuperuser --username=teacherWang --email=wangchongyang@126.com 
上述命令会提示你输入密码。输入密码后,立即创建指定的超级用户。
如果没有指定 --username 或 --email 选项,会提示你输入这两个值。
Email address: wangchongyang@126.com
Password: 
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: n
Password: 
Password (again):
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully. 
最终看到 Superuser created successfully说明创建成功了。
1.2 创建用户
创建和管理用户最简单、最不易出错的方式是使用 Django 管理后台。
管理后台的使用我们在第七章((1条消息) <学习笔记>从零开始自学Python-之-web应用框架Django( 七)Django管理后台_阿尔法羊的博客-CSDN博客)详细讲过了
当然也可以用Django内置的辅助函数create_user()来实现同样的功能
from django.contrib.auth.models import User 
user = User.objects.create_user('teacherWang', 'wangchongyang@126.com', 'password') 
# 此时,user 是一个 User 对象,而且已经保存到数据库中 
# 如果想修改其他字段的值,可以继续修改属性 
user.last_name = 'wang' 
user.save() 
1.3 在 Web 请求中验证身份
Django 使用会话和中间件把身份验证系统插入 request 对象,为每个请求提供 request.user 属性,表示当前用户。如果未登陆,这个属性的值是一个 AnonymousUser 实例,否则是是一个 User 实例。
这两种情况可以使 用 is_authenticated() 方法区分,例如:
if request.user.is_authenticated(): 
    # 处理通过身份验证的用户 
else: 
    # 处理匿名用户 
2、在视图中操作身份验证
2.1 登录与退出
2.1.1 在视图中使用 login() 登录用户。
它的参数是一个 HttpRequest 对象和一个 User 对象。login() 使用 Django 的会话框架把用户的 ID 保存到会话中。注意,匿名期间设定的会话数据在用户登录后依然存在。
下述示例 展示 authenticate() 和 login() 的用法:
from django.contrib.auth import authenticate, login 
def my_view(request): 
    username = request.POST['username'] 
    password = request.POST['password']
    user = authenticate(username=username, password=password) 
    if user is not None: 
        if user.is_active: 
            login(request, user) 
            # 重定向到成功登录页面 
        else: 
            # 返回“账户未激活”错误消息 
    else: 
        # 返回“无效登录”错误消息 
注意:自己动手登录用户时,必须在 login() 之前调用 authenticate()。authenticate() 在 User 对象 上设定一个属性,指明成功验证用户身份的是哪个身份验证后端,而登录过程中需要使用这个信息。如果直接登录从数据库中检索的用户对象,Django 会报错。
2.1.2 在视图中使用logout()退出
在视图中退出通过 login() 登录的用户使用 logout()。这个函数的参数是一个 HttpRequest 对象,而且没有返回值。
例如:
from django.contrib.auth import logout 
def logout_view(request): 
    logout(request) 
    # 重定向到成功退出页面  
注意,如果用户未登录,logout() 函数不报错。调用 logout() 函数后,当前请求的会话数据完全清除,所有 数据将被删除。这样能避免其他人在登录的 Web 浏览器中访问用户之前的会话数据。 如果想让会话中的数据在退出后依然可用,调用 logout() 函数之后再把数据存入会话。
2.2 限制已登录用户的访问
2.2.1 直接方式
限制访问页面简单直接的方式是检查 request.user.is_authenticated(),如果未通过,可以重定向到登录页面:
from django.shortcuts import redirect 
def my_view(request): 
    if not request.user.is_authenticated(): 
        return redirect('/login/?next=%s' % request.path) 
        
# ... 也可以显示一个错误消息: 
from django.shortcuts import render 
def my_view(request): 
    if not request.user.is_authenticated():
        return render(request, 'books/login_error.html') 
 
2.2.2 装饰器
也可以使用便利的 login_required() 装饰器:
from django.contrib.auth.decorators import login_required 
@login_required 
def my_view(request): 
    ... 
login_required() 的作用如下:
• 如果用户未登录,重定向到 LOGIN_URL,并把当前绝对路径添加到查询字符串中。
例如:/accounts/ login/?next=/reviews/3/。
• 如果用户已登录,正常执行视图。视图代码可以放心假定用户已登录。
默认,成功通过身份验证后重定向的目标路径存储在名为 next 的查询字符串参数中。如果想为这个参数提供 其他名称,可以设定 login_required() 可选的 redirect_field_name 参数:
from django.contrib.auth.decorators import login_required
 
@login_required(redirect_field_name='my_redirect_field') 
def my_view(request): 
    ... 
注意,如果为 redirect_field_name 提供了值,可能还要定制登录模板,因为模板上下文中存储重定向路径的变量名是 redirect_field_name 的值,而不再是默认的 next。
login_required() 还有个可选的 login_url 参 数。例如:
from django.contrib.auth.decorators import login_required
 
@login_required(login_url='/accounts/login/') 
def my_view(request): 
    ... 
注意,如果不指定 login_url 参数,要确保把 LOGIN_URL 设为正确的登录视图。例如,使用默认配置时,要 把下述代码添加到 URL 配置中:
from django.contrib.auth import views as auth_views 
url(r'^accounts/login/$', auth_views.login), 
LOGIN_URL 的值还可以是视图函数名称或具名 URL 模式。这样,无需修改设置就可以在 URL 配置中自由映射登录视图。
2.3 permission_required 装饰器
检查用户有没有特定权限是比较常见的任务。鉴于此,Django 提供了一种简便的方式——permission_required() 装饰器:
from django.contrib.auth.decorators import permission_required @permission_required('reviews.can_vote') 
def my_view(request): 
    ... 
与 has_perm() 方法一样,参数名称的形式为“.”(例如,reviews.can_vote 是 reviews 应用中某个模型定义的权限)。这个装饰器的参数也可以是一个权限列表。注意,permission_required() 也有可选的 login_url 参数。例如:
from django.contrib.auth.decorators import permission_required @permission_required('reviews.can_vote', login_url='/loginpage/') 
def my_view(request):
    ... 
与 login_required() 装饰器一样,login_url 的默认值是 LOGIN_URL。如果指定了 raise_exception 参数,这 个装饰器不会重定向到登录页面,而是抛出 PermissionDenied 异常,显示 403(HTTP Forbidden)视图。
2.4、身份验证视图
Django 为登录、退出和密码管理提供了视图。这些视图使用 auth 包中内置的表单,不过也可以传入自己编写的视图。Django 没有为身份验证视图提供默认的模板,不过下文将说明各个视图的模板上下文。
在项目中使用这些视图要实现不同的方法,不过最简单也是最常见的做法是把 django.contrib.auth.urls 提 供的 URL 配置添加到项目的 URL 配置中。例如:
urlpatterns = [url('^', include('django.contrib.auth.urls'))]
这样,各个视图在默认的 URL 上(后文详述)。 这些内置的视图都返回一个 TemplateResponse 实例,这样便于在渲染之前定制响应数据。多数内置的身份验 证视图提供了 URL 名称,易于引用。
2.4.1 login 视图
登录用户。
默认 URL:/login/。
可选参数:
• template_name:这个视图使用的模板名称。默认为 registration/login.html。
• redirect_field_name:GET 参数中指定登录后重定向 URL 的字段名称。默认为 next。
• authentication_form:验证身份的可调用对象(通常是一个表单类)。默认为 AuthenticationForm。
• current_app:一个提示,指明当前视图所在的应用。
• extra_context:一个字典,包含额外的上下文数据,随默认的上下文数据一起传给模板。 login 视图的作用如下:
• 如果通过 GET 调用,显示登录表单,其目标地址与当前 URL 一样。稍后详述。
• 如果通过 POST 调用,发送用户提交的凭据,尝试登录用户。如果登录成功,重定向到 next 指定的 URL。如果没有 next,重定向到 LOGIN_REDIRECT_URL(默认为 /accounts/profile/)。如果登录失败,重新显示登录表单。 登录视图的模板由你提供,模板文件默认名为 registration/login.html。
模板上下文:
• form:表示 AuthenticationForm 的 Form 对象。
• next:成功登录后重定向的目标 URL。自身可能也包含查询字符串。
• site:当前 Site,根据 SITE_ID 设置确定。如果未安装网站框架,其值默认为 RequestSite 实例,从 当前 HttpRequest 对象中获取网站名称和域名。
• site_name:site.name 的别名。如果未安装网站框架,其值为 request.META['SERVER_NAME'] 的值。 如果不想把模板命名为 registration/login.html,可以为 URL 配置提供额外的参数,设定 template_name 参数。
2.4.2 logout 视图
退出用户。
默认 URL:/logout/
可选的参数:
• next_page:退出后重定向的目标 URL。
• template_name:一个模板全名,在用户退出后显示。如果未提供这个参数,默认为 registration/ logged_out.html。
• redirect_field_name:GET 参数中指定退出后重定向 URL 的字段名称。默认为 next。如果提供这个参数,next_page 将被覆盖。
• current_app:一个提示,指明当前视图所在的应用。
• extra_context:一个字典,包含额外的上下文数据,随默认的上下文数据一起传给模板。
模板上下文:
• title:本地化之后的字符串“Logged out”。
• site:当前 Site,根据 SITE_ID 设置确定。如果未安装网站框架,其值默认为 RequestSite 实例,从 当前 HttpRequest 对象中获取网站名称和域名。
• site_name:site.name 的别名。如果未安装网站框架,其值为 request.META['SERVER_NAME'] 的值。
2.4.3 logout_then_login 视图
退出用户,然后重定向到登录页面。
默认 URL:未提供。
可选的参数:
• login_url:重定向到的登录页面的 URL。如果未提供,默认为 LOGIN_URL。
• current_app:一个提示,指明当前视图所在的应用。
• extra_context:一个字典,包含额外的上下文数据,随默认的上下文数据一起传给模板。
2.5 内置的表单
如果不想使用内置的视图,也不想为这些功能编写表单,可以使用身份验证系统在 django.contrib.auth.forms 中内置的几个表单(表 11-1)。 这些内置的表单对用户模型有些假设,如果自定义了用户模型,可能要自己动手为身份验证系统编写表单。
| 表单名 | 说明 | 
|---|---|
| AdminPasswordChangeForm | 在管理后台中用于修改用户密码的表单。第一个位置参数是用户对象。 | 
| AuthenticationForm | 登录表单。请求对象是第一个位置参数,存储在表单中,供子类使用。 | 
| PasswordChangeForm | 修改密码的表单。 | 
| PasswordResetForm | 用于生成并发送带有重设密码链接的电子邮件。 | 
| SetPasswordForm | 让用户修改密码的表单,无需输入旧密码。 | 
| UserChangeForm | 在管理后台中用于修改用户信息和权限的表单。 | 
| UserCreationForm | 创建新用户的表单。 | 
2.6 模板中的身份验证数据
使用 RequestContext 时,当前登录用户及其权限可通过模板上下文访问。
2.6.1 用户
渲染模板的 RequestContext 时,当前登录用户,不管是 User 实例还是 AnonymousUser 实例,都存储在模板变 量 {{ user }} 中:
  
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}. Thanks for logging in.</p>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %} 
如果使用的不是 RequestContext,这个模板上下文变量不可用。
2.6.2 权限
当前登录用户的权限存储在模板变量 {{ perms }} 中。它的值是 django.contrib.auth.context_processors.PermWrapper 的一个实例,对模板友好。在 {{ perms }} 对象中,单属性查找由 User.has_module_perms 代理。只要当前登录用户在 foo 应用中有权限,下述示例就返回 True:
{{ perms.foo }}
两层属性查找由 User.has_perm 代理。如果当前登录用户有 foo.can_vote 权限,下述示例返回 True:
{{ perms.foo.can_vote }}
因此,在模板中可以使用 {% if %} 语句检查权限:
{% if perms.foo %}
<p>You have permission to do something in the foo app.</p>
{% if perms.foo.can_vote %}
<p>You can vote!</p>
{% endif %} {% if perms.foo.can_drive %}
<p>You can drive!</p>
{% endif %} {% else %}
<p>You don't have permission to do anything in the foo app.</p>
{% endif %} 
此外,还可以使用 {% if in %} 语句检查权限。
{% if 'foo' in perms %}
{% if 'foo.can_vote' in perms %}
<p>In lookup works, too.</p>
{% endif %}
{% endif %}
 
3、案例:自己动手做一个网站用户系统
基于我们前面已经做好的网站,我们现在做一个简单的用户登录和注册系统,然后设置权限
3.1 登录系统
先写一个简单的登录页面login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<div class="container" style="margin-top: 100px">
    <div class="row">
        <div>
            <div>
                <div>
                    <h3>登录</h3>
                </div>
                <div>
                    <form action="" method="post" novalidate>
                        {% csrf_token %}
 
{#                        方法一: 写出form表单中的指定字段 #}
{#                        {{ login_form.username.label_tag }}:#}
{#                        {{ login_form.username }}#}
{#                        <p>{{ login_form.errors.username.0 }}</p>#}
{#                        {{ login_form.password.label_tag }}:#}
{#                        {{ login_form.password }}#}
{#                        <p>{{ login_form.errors.password.0 }}</p>#}
{#                        <div>{{ login_form.non_field_errors }}</div>#}
 
{#                        方法二: 遍历出form表单中的所有字段 #}
                        {% for field in login_form %}
                            {{ field.label_tag }}
                            {{ field }}
                            <p>{{ field.errors.as_text }}</p>
                        {% endfor %}
{#                        此处错误信息会返回clean联合校验,也就是非单个字段校验的错误信息#}
                        <span>{{ login_form.non_field_errors }}</span>
 
                        <input type="submit" value="登录" class="btn">
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
<br>
<div>
    <a href="/register">注册</a>
</div>
</body>
</html> 
然后我们在forms.py里面创建一个LoginForm类,用于接收前端表单输入的数据,并实现验证
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
 
 
class LoginForm(forms.Form):
    username = forms.CharField(
        label="用户名",
        min_length=3,
        widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入用户名"}),
        error_messages={
            "required": "用户名不能为空",
            "min_length": "用户名最小长度为3位",
        },
    )
    password = forms.CharField(
        label="密码",
        min_length=6,
        error_messages={
            "min_length": "密码最小长度为6位",
            "required": "密码不能为空",
        },
        widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "请输入密码"}),
    )
 
    def clean(self):
        username = self.cleaned_data.get("username", "")
        password = self.cleaned_data.get("password", "")
        user = authenticate(username=username, password=password)
        if not user:
            raise forms.ValidationError("用户名或密码错误")
 
        self.cleaned_data["user"] = user
        return self.cleaned_data 
然后在views.py 里面写好一个登录的函数
def user_login(request):
    if request.method == "GET":
        username = request.GET.get("username")
        login_form = LoginForm()
        return render(request, "login.html", context={"login_form": login_form})
    elif request.method == "POST":
        login_form = LoginForm(request.POST)
        if login_form.is_valid():
            # 注意:验证用户名和密码是否正确放到forms中去验证了
            # login(request, request.user)  # 此处不能使用request.user,因为他还没有验证,是匿名用户
            # 所以需要在form中校验通过后传递过来user
            login(request, login_form.cleaned_data["user"])
            user = request.POST["username"]
            next = request.GET.get("next", reverse("index"))
            return redirect(next,{'user':user})
        else:
            return render(request, "login.html", {"login_form": login_form}) 
如果从form类返回的验证正确,就跳转到指定网页,这里指定到 index 页面,注意这里这个 index 是 路由的名字(name,就是设置path时候指定的name)。
最后在urls.py 里面加上路由
path('login/',user_login,name="login"), 
我们访问http://127.0.0.1:8000/login

因为没有加上css,界面看上去比较朴素,但是已经可以使用了
3.2 注册系统
当然这时候我们只有一个前面创建的超级用户,要想让普通用户注册使用,还要做一个注册系统
一样,先写好前端的表单页面 register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<div class="container" style="margin-top: 100px">
    <div class="row">
        <div>
            <div>
                <div>
                    <h3>注册</h3>
                </div>
                <div>
                    <form action="" method="post" novalidate>
 
                        {% csrf_token %}
                        {% for field in register_form %}
                            {{ field.label_tag }}
                            {{ field }}
                            <p>{{ field.errors.as_text }}</p>
                        {% endfor %}
                        <span>{{ register_form.non_field_errors }}</span>
                        <input type="submit" value="注册">
 
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html> 
在forms.py里面写一个注册用的 RegisterForm类
class RegisterForm(LoginForm):
    password_again = forms.CharField(
        label="确认密码",
        min_length=6,
        error_messages={
            "required": "确认密码不能为空",
            "min_length": "密码最小长度为6位",
        },
        widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "确认密码"}),
    )
    email = forms.EmailField(
        label="邮箱",
        required=False,
        error_messages={
            "required": "密码不能为空",
        },
        widget=forms.EmailInput(attrs={"class": "form-control", "placeholder": "请输入邮箱"})
    )
 
    def clean_username(self):
        username = self.cleaned_data["username"]
        if User.objects.filter(username=username).exists():
            raise forms.ValidationError("用户名已存在")
        return username
 
    def clean_email(self):
        email = self.cleaned_data["email"]
        if email and User.objects.filter(email=email).exists():
            raise forms.ValidationError("邮箱已存在")
 
        return email
 
    def clean(self):
        password = self.cleaned_data.get("password", "")
        password_again = self.cleaned_data.get("password_again", "")
 
        if password != password_again:
            raise forms.ValidationError("两次密码不一致,请重新输入")
 
        return self.cleaned_data 
再在views.py里面 写好视图函数
def user_register(request):
    if request.method == "GET":
        register_form = RegisterForm()
        return render(request, "register.html", context={"register_form": register_form})
    elif request.method == "POST":
        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            username = register_form.cleaned_data["username"]
            password = register_form.cleaned_data["password"]
            email = register_form.cleaned_data.get("email", "")
            # 创建用户
            user = User.objects.create_user(username, email, password)
            next = request.GET.get("next", reverse("index"))
            return redirect(next)
        else:
            return render(request, "register.html", {"register_form": register_form}) 
最后加上路由
path('register/',user_register,name="register"), 
我们访问 http://127.0.0.1:8000/register

我们注册一个名为testone的用户,登录之后可以看到, 用户名正确显示出来了
 
3.3 退出登录
这里我们前端加了一个退出登录的功能按钮
Django内置了一个logout 函数,实现logout功能非常容易
def user_logout(request):
    logout(request)
    return redirect("login") 
3.4 设置权限
既然使用了用户系统,当然对用户访问就要做些限制,我们在主页视图函数前加上装饰器,并指定如果未登录时候的跳转页面(如果不指定,默认的跳转页面是('/accounts/login/')
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def classnotice(request):
    print(request.GET)
    now=datetime.datetime.now()
    context={}
    context['student_list']=Student.objects.all().order_by('-score')
    context['teacher']='王重阳'
    context['now']=now
    
    return render(request,'class3.html',context=context,status=200) 
再访问主页,就会跳转到登录页面
最后我们再来设置一下权限。比如之前我们做了一个add_student的表单,用于增加Student模型的实例。现在我们给这个功能加上权限,这样没有权限的用户就会退到登录界面
from django.contrib.auth.decorators import permission_required
@permission_required('classManage.add_student',login_url='/login/')
def add_student(request):
    if request.method == 'GET':
        student_list = addStudent()
        return render(request,'addStudent.html',{'students':student_list},status=200)
    else:
        student_list = addStudent(request.POST)
        if student_list.is_valid():
            student_list.save()
        return render(request,'addStudent.html',{'student_list':student_list},status=200) 
这时候,我们用teacherWang账户登录可以访问 ‘/add_student/’,但是用testone账户访问'/add_student'就会跳转到登录界面。
我们用程序给testone账户赋予权限
from classManage.models import Student
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
user = User.objects.get(username='testone')
permission = Permission.objects.get(codename='add_student')
user.user_permissions.add(permission)
 
然后再用testone账户登录,再访问http://127.0.0.1:8000/add_student
发现可以访问了





![[附源码]SSM计算机毕业设计疫情期间物资分派管理系统JAVA](https://img-blog.csdnimg.cn/b59213a991bd4a61bc184cf10496afb8.png)


![[附源码]Python计算机毕业设计Django个人博客系统](https://img-blog.csdnimg.cn/6bb236949fd74c6d80fb701312c2a757.png)





![[附源码]SSM计算机毕业设计血库管理系统JAVA](https://img-blog.csdnimg.cn/39641a460fa94577bb546996551b561f.png)





