Django之用户认证Auth组件

用户认证Auth

概述

Django 用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法,并跳转到登陆成功或失败页面。

Django认证组件的重要信息:

用户模型:

Django的认证组件使用的默认用户模型(User)存储用户信息,包括用户名、密码和电子邮件地址。通过配置,可以使用扩展的用户模型(例如添加额外的自定义字段)。

视图:

Django的认证组件包含了一系列视图,用于处理用户认证(登录、注销、密码重置)、用户管理(注册、修改密码、更改用户信息)以及权限管理。

身份验证后端(Authentication backends):

Django的认证组件使用身份验证后端来验证用户身份。默认情况下,Django提供基于用户名和密码的身份验证后端,但是你也可以编写自己的身份验证后端来满足你的特定需求。

用户组和权限:

扫描二维码关注公众号,回复: 14777983 查看本文章

Django的认证组件允许创建用户组,为这些用户组分配权限,并控制哪些用户可以访问特定的视图。

Django用户认证组件需要导入auth模块:

# 认证模块
from django.contrib import auth

# 对应数据库
from django.contrib.auth.models import User

User模型类

Django框架默认使用一个User模型类, 保存有关用户的字段,使用auth_user表存储。

User模型类继承自AbstractUser类
在这里插入图片描述
AbstractUser类
在这里插入图片描述
User对象基本属性

创建用户必选: username、password

创建用户可选:email、first_name、last_name、last_login、date_joined、is_active 、is_staff、is_superuse

判断用户是否通过认证:is_authenticated

创建用户

创建用户对象的三种方法:

create():创建一个普通用户,密码是明文

create_user():创建一个普通用户,密码是密文

create_superuser():创建一个超级用户,密码是密文,要多传一个邮箱email参数

参数:

username: 用户名

password:密码

email:邮箱 (create_superuser 方法要多加一个 email)
from django.contrib.auth.models import User 

User.objects.create(username='john',password='password')

user = User.objects.create_user('john', '[email protected]', 'password')

User.objects.create_superuser(username='john',password='password',email='[email protected]')

身份验证

验证用户的用户名和密码使用authenticate() 方法,从需要 auth_user 表中过滤出用户对象

参数:

username:用户名

password:密码

返回值:如果验证成功,就返回用户对象,反之,返回 None。

from django.contrib import auth

# 通过用户名和密码进行身份验证
user_obj = auth.authenticate(username=username, password=password)
print(user_obj.username)

登陆

给验证成功的用户加 session,将 request.user 赋值为用户对象。

user_obj = auth.authenticate(username=username, password=password)
print(user_obj.username)

if not user_obj:
     return redirect("/login/")
else:
    auth.login(request, user_obj)
    path = request.GET.get("next") or "/index/"
    print(path)
    return redirect(path)

注销

注销用户使用 logout() 方法,需要清空 session 信息,将 request.user 赋值为匿名用户。

def logout(request):
    auth.logout(request)
    return redirect("/login/")

判断用户是否登录

is_authenticate

Django用户认证系统提供了方法request.user.is_authenticated()来判断用户是否登录。如果通过登录验证则返回True。反之,返回False。

缺点:凡是需要登录验证都需要判断逻

LoginRequiredMixin封装了判断用户是否登录的操作。

from django.contrib.auth.mixins import LoginRequiredMixin

class UserInfoView(LoginRequiredMixin, View):
    def get(self, request):
        return render(request, 'user_center_info.html')
class UserInfoView(View):

    def get(self, request):
        """个人信息界面"""
        
        # 判断是否登录验证
        if request.user.is_authenticated():
            # 如果登录, 则加载用户中心页面
            return render(request, 'user_center.html')
        else:
            # 否则, 进入登录页面,进行登录
            return redirect(reverse('user:login'))

@login_required装饰器

Django的用户认证系统提供了@login_required这个装饰器来判断用户是否登录,其内部封装了 is_authenticate

1.装饰函数视图

装饰器可以直接装饰函数视图

from django.contrib.auth.decorators import login_required

@login_required
def index(request):
	# 该视图需要登录才能访问
    return HttpResponse("index页面。。。")

2.装饰类视图

要想使用 login_required装饰器装饰类视图,可以间接的装饰as_view()方法的返回值,as_view()方法就是将类视图转成的函数视图

from django.contrib.auth.decorators import login_required
from django.urls import path, re_path

from user.views import UserInfoView

urlpatterns = [
    re_path(r'^userInfo/$', login_required(UserInfoView.as_view()), name='userInfo'),
]

3.封装装饰器

不推荐对as_view() 方法添加装饰行为,因为url.py 文件只是一个加载路由的文件, 把逻辑写在这个文件不好

1.定义 View 子类封装 login_required 装饰器

# 继承自: View
class LoginRequired(View):
    """验证用户是否登陆的工具类"""

    # 重写as_view() , 对as_view()进行装饰
    @classmethod
    def as_view(cls, **initkwargs):
        # 重写这个方法, 不做任何的修改操作, 直接调用父类的 super().as_view() 函数.
        view = super().as_view()
        return login_required(view)

url配置还原

urlpatterns = [
    re_path(r'^userInfo/$', UserInfoView.as_view(), name='userInfo'),
]

让需要认证登录的的类视图继承LoginRequired

class UserInfoView(LoginRequired):

    def get(self, request):
        return render(request, 'user_info.html')

2.定义 obejct子类封装login_required装饰器

工具类直接依赖于视图类 View,所以复用性相对来说很差,定义obejct子类封装login_required装饰器

更好的做法是将将这个工具类封装成一个扩展类,在views.py中进行导入

class LoginRequired(object):

    # 重写as_view() , 对as_view()进行装饰
    @classmethod
    def as_view(cls, **initkwargs):
        # 重写这个方法, 不做任何的修改操作, 直接调用父类的 super().as_view() 函数.
        view = super().as_view()
        return login_required(view)


# 需要认证登录的的类视图继承工具类 + View
class UserInfoView(LoginRequired, View):

    def get(self, request):
        return render(request, 'user_info.html')

如果通过登录验证则进入到视图内部,执行视图逻辑。如果未通过登录验证则被重定向到LOGIN_URL 配置项指定的地址

在项目的 settings.py文件配置

# 默认重定向
# LOGIN_URL = '/accounts/login'

# 表示当用户未通过登录验证时,将用户重定向到登录页面
LOGIN_URL = '/user/login/'

next参数

next参数记录了用户未登录时访问的地址信息,可以帮助实现在用户登录成功后直接进入未登录时访问的地址。

简单的说就是next参数用于设置从哪个页面访问,登录成功后就返回哪个页面。

#  获取跳转过来的地址
next = request.GET.get('next')
if next:
	# 如果是从别的页面跳转过来的, 则重新跳转到原来的页面
    response = redirect(next)
else:
	 # 如果是直接登陆成功,就重定向到首页
    response = redirect(reverse('user:index'))

如果get() 就取不到值,path可以自定义设置返回的页面

path = request.GET.get("next") or "/index/"
return redirect(path)

修改密码

打开Terminal,执行以下代码,然后输入新的密码。此方法会校验密码强度,不能设置过于简单的密码)

python manage.py changepassword 用户名

打开Terminal,进入shell环境,执行以下代码,此方法不会校验密码强度,可设置简单的密码

from django.contrib.auth.models import User
user = User.objects.get(username='用户名')
user.set_password('新的密码')
user.save()

使用Django提供的PasswordChangeForm表单来实现修改密码

如果是请求是POST,则使用PasswordChangeForm表单来验证并保存修改密码的请求,然后重定向到index。

如果请求是GET,将渲染一个模板,显示一个表单,让用户输入当前密码和新密码。
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect


@login_required
def change_password(request):
    if request.method == 'POST':
        form = PasswordChangeForm(user=request.user, data=request.POST)
        # 进行数据校验
        if form.is_valid():
            form.save()
            return redirect(reverse('user_app:index'))
    else:
        form = PasswordChangeForm(user=request.user)
    return render(request, 'change_password.html', {
    
    'form': form})

创建URL模式

from user.views import IndexView, change_password

urlpatterns = [
    re_path('index/', IndexView.as_view(), name='index'),
    path('change_password/', change_password, name='change_password'),
]

创建修改密码表单模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

  <h2>修改密码</h2>
  <form method="post">
    {
    
    % csrf_token %}
    {
    
    {
    
     form.as_p }}
    <button type="submit">提交</button>
  </form>

</body>
</html>

先进行用户登录,然后访问http://127.0.0.1:8000/user/change_password/修改密码
在这里插入图片描述

自定义用户模型类

当Django默认用户模型类中没有需要字段时,就需要自定义用户模型类。

自义用户模型类只需要继承AbstractUser,然后新增需要的额外字段即可,如新增mobile字段

from django.contrib.auth.models import AbstractUser
from django.db import models


# 重写用户模型类, 继承AbstractUser
class User(AbstractUser):
    """自定义用户模型类"""

    # 增加mobile字段
    mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')

    # 对当前表进行相关设置: 
    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

    # str方法中, 返回用户名称
    def __str__(self):
        return self.username

Django中默认使用auth子应用下面的User作为用户模型类。即Django用户模型类是通过全局配置项AUTH_USER_MODEL决定的
在这里插入图片描述
因为重写用户模型类, 所以需要在settings.py文件重新指定默认的用户模型类

AUTH_USER_MODEL = '应用名.模型类名'

生成表

# 生成迁移文件
python manage.py makemigrations

# 进行数据迁移
python manage.py migrate
mysql> show tables;
+---------------------------+
| Tables_in_demo            |
+---------------------------+
| auth_group                |
| auth_group_permissions    |
| auth_permission           |
| django_admin_log          |
| django_content_type       |
| django_migrations         |
| django_session            |
| tb_users                  |
| tb_users_groups           |
| tb_users_user_permissions |
+---------------------------+
10 rows in set (0.00 sec)

注册

项目 配置urls.py

from django.contrib import admin
from django.urls import path, include, re_path

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r'^user/*/', include(('user.urls', 'user_app'), namespace='user'))
]

应用 配置urls.py

from user.views import RegisterView, IndexView

urlpatterns = [
    re_path('index/', IndexView.as_view(), name='index'),
    re_path('register/', RegisterView.as_view(), name='register'),
]

注册功能实现

import re

from django import http
from django.contrib.auth import login
from django.db import DatabaseError
from django.shortcuts import redirect
from django.shortcuts import render
from django.urls import reverse
from django.views import View
from django_redis import get_redis_connection

from user.models import User


class IndexView(View):

    def get(self, request):
        return render(request, 'index.html')

    def post(self, request):
        return render(request, 'index.html')


class RegisterView(View):

    def get(self, request):
        return render(request, 'register.html')

    def post(self, request):
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        sms_code = request.POST.get('sms_code')

        # 判断参数是否齐全
        if not all([username, password, password2, mobile, sms_code]):
            return http.HttpResponseBadRequest('缺少必传参数')
        # 判断用户名是否是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_]{5,20}$', username):
            return http.HttpResponseBadRequest('请输入5-20个字符的用户名')
        # 判断密码是否是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseBadRequest('请输入8-20位的密码')
        # 判断两次密码是否一致
        if password != password2:
            return http.HttpResponseBadRequest('两次输入的密码不一致')
        # 判断手机号是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseBadRequest('请输入正确的手机号码')

        # redis 短信验证码
        sms_code_client = request.POST.get('sms_code')
        redis_conn = get_redis_connection('default')
        sms_code_server = redis_conn.get('sms_%s' % mobile)
        if sms_code_server is None:
            return render(request, 'register.html', {
    
    'msg': '无效的短信验证码'})
        if sms_code_client != sms_code_server.decode():
            return render(request, 'register.html', {
    
    'msg': '输入短信验证码有误'})

        # 保存注册数据
        try:
            user = User.objects.create_user(username=username, password=password, mobile=mobile)
        except DatabaseError:
            return render(request, 'register.html', {
    
    'msg': '注册失败'})

        # 实现状态保持
        login(request, user)

        # 响应注册结果
        response = redirect(reverse('user_app:index'))

        # 注册时用户名写入到cookie,有效期15天
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

        return response

在这里插入图片描述

用户名登录

Django自带的用户认证后端默认是使用用户名实现用户认证的。用户认证后端位置:django.contrib.auth.backends.ModelBackend。

应用 配置urls.py

from user.views import LoginView

urlpatterns = [
    re_path(r'^login/$', LoginView.as_view(), name='login'),
]

登录功能实现

class LoginView(View):
    def get(self, request):
        return render(request, "login.html")

    def post(self, request):
        # 接受参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')

        # 判断参数是否齐全
        if not all([username, password]):
            return http.HttpResponseBadRequest('缺少必传参数')

        # 判断用户名是否是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseBadRequest('请输入正确的用户名或手机号')

        # 判断密码是否是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseBadRequest('密码最少8位,最长20位')

        # 认证登录用户
        user = authenticate(username=username, password=password)
        if user is None:
            return render(request, 'login.html', {
    
    'msg': '用户名或密码错误'})

        # 实现状态保持
        login(request, user)
        # 设置状态保持的周期
        if remembered != 'on':
            # 没有记住用户:浏览器会话结束就过期
            request.session.set_expiry(0)
        else:
            # 记住用户:None表示两周后过期
            request.session.set_expiry(None)

        # 响应登录结果
        response = redirect(reverse('user_app:index'))

        # 登录时用户名写入到cookie,有效期15天
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

        return response

在这里插入图片描述

退出登录

Django用户认证系统提供了logout()方法,封装了清理session的操作,帮助快速实现登出一个用户

from django.contrib.auth import logout

class LogoutView(View):
    def get(self, request):
        # 清理session
        logout(request)
        # 退出登录,重定向到登录页
        response = redirect(reverse('user:index'))
        # 退出登录时清除cookie中的username
        response.delete_cookie('username')

        return response
from django.urls import include, path, re_path

from . import views

urlpatterns = [
    re_path(r'^logout/$', views.LogoutView.as_view(), name='logout'),
]

修改密码

class PasswordUpdateView(LoginRequiredMixin,View):

    def get(self, request):
        return render(request, 'change_password.html')

    def post(self, request):
        # 参数
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        new_password2 = request.POST.get('new_password2')

        # 校验参数
        if not all([old_password, new_password, new_password2]):
            return http.HttpResponseForbidden('缺少必传参数')
        try:
            request.user.check_password(old_password)
        except Exception as e:
            logger.error(e)
            return render(request, 'change_password.html', {
    
    'msg': '原始密码错误'})
        if not re.match(r'^[0-9A-Za-z]{8,20}$', new_password):
            return http.HttpResponseForbidden('密码最少8位,最长20位')
        if new_password != new_password2:
            return http.HttpResponseForbidden('两次输入的密码不一致')

        # 修改密码
        try:
            request.user.set_password(new_password)
            request.user.save()
        except Exception as e:
            logger.error(e)
            return render(request, 'change_password.html', {
    
    'msg': '修改密码失败'})

        # 重定向到登录界面
        response = redirect(reverse('user:index'))
        # 清理状态保持信息
        response.delete_cookie('username')

        return response

路由

urlpatterns = [
    re_path('index/', IndexView.as_view(), name='index'),
    re_path('register/', RegisterView.as_view(), name='register'),
    re_path(r'^login/$', LoginView.as_view(), name='login'),
    re_path('change_password/', PasswordUpdateView.as_view(), name='change_password'),
]

change_password.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


  <h2>修改密码</h2>
  <form method="post">
    {
    
    % csrf_token %}
      {
    
    {
    
    msg}}<br>
      
    <label for="password">old_password:</label>
    <input type="text" id="username" name="old_password"><br>
    <label for="password">new_assword1:</label>
    <input type="password" id="password" name="new_password"><br>

     <label for="password">new_assword2:</label>
    <input type="password" id="password" name="new_password2"><br>
    <button type="submit">提交</button>
  </form>

</body>
</html>

多账号登录

如果想实现用户名和手机号都可以认证用户,就需要自定义用户认证后端。

认证后端步骤:

新建类,继承自ModelBackend

重写认证authenticate()方法

分别使用用户名和手机号查询用户

返回查询到的用户实例

自定义用户认证后端

在users应用中新建utils.py文件

import re

from django.contrib.auth.backends import ModelBackend

from .models import User


def get_user_by_account(account):
    """
    根据account查询用户
    :param account: 用户名或者手机号
    :return: user
    """
    try:
        if re.match('^1[3-9]\d{9}$', account):
            # 手机号登录
            user = User.objects.get(mobile=account)
        else:
            # 用户名登录
            user = User.objects.get(username=account)
    except User.DoesNotExist:
        return None
    else:
        return user


class UsernameMobileAuthBackend(ModelBackend):
    """自定义用户认证后端"""

    def authenticate(self, request, username=None, password=None, **kwargs):
        """
        重写认证方法,实现多账号登录
        :param request: 请求对象
        :param username: 用户名
        :param password: 密码
        :param kwargs: 其他参数
        :return: user
        """
        # 根据传入的username获取user对象。username可以是手机号也可以是账号
        user = get_user_by_account(username)
        # 校验user是否存在并校验密码是否正确
        if user and user.check_password(password):
            return user

配置自定义用户认证

Django默认用户认证是通过全局配置项AUTH_USER_MODEL决定,默认配置如下:

AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"]

在settings.py文件重新指定默认的用户认证

# 指定自定义的用户认证后端
AUTHENTICATION_BACKENDS = [
    # 'django.contrib.auth.backends.ModelBackend',
    'apps.users.utils.UsernameMobileAuthBackend'
]

登录测试

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_38628046/article/details/129840016