• 【Django】认证系统


    @


    我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。

    Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。

    补充:Django内置的生成当前时间的方法

    from django.utils.timezone import now
    now_time = now()  # 生成当前时间
    # 时间格式示例:2018-11-22 05:17:09.538525+00:00
    # 它生成的时间格式能够与数据库中的auto_now/auto_now_add生成的时间做计算.
    


    #. auth模块

    # 导入auth模块
    from django.contrib import auth
    

    1. 认证 authenticate()

    提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。

    如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。

    authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。

    用法:

    # user = auth.authenticate(request, **form_obj.cleaned_data)
    user = auth.authenticate(request, username="用户名", password="密码")
    

    2. 登陆 login(HttpRequest, user)

    该函数接受一个HttpRequest对象,以及一个经过认证的User对象。

    该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。

    session数据表名为:django_session,一个浏览器对应一条记录。

    示例:
    在这里插入图片描述


    3. 注销 logout(request)

    该函数接受一个HttpRequest对象,无返回值。

    当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。

    示例:
    在这里插入图片描述


    4. 认证判断 is_authenticated()

    用来判断当前请求是否通过了认证(用户是否登陆)。

    示例:
    在这里插入图片描述


    5. 登陆校验 login_requierd()

    auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。

    若用户没有登录,则会跳转到django默认的登录URL '/accounts/login/ ' ,并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。

    如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改,如下:

    # 修改默认的登陆URL为'/login/':
    LOGIN_URL = '/login/'
    

    用法:

    # 用于验证是否登陆的装饰器
    from django.contrib.auth.decorators import login_required  
    
    @login_required
    def home(request):
    	pass
    

    6. 创建普通用户 create_user()

    auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。

    示例:
    在这里插入图片描述


    7. 创建超级用户 create_superuser()

    auth 提供的一个创建新的超级用户的方法,需要提供必要参数(email, username, password)。

    示例:
    在这里插入图片描述


    8. 密码校验 check_password(password)

    auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。

    密码正确返回True,否则返回False。

    示例:
    在这里插入图片描述


    9. 修改密码 set_password(new_password)

    auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。

    注意:设置完一定要调用用户对象的save方法!!!

    示例:
    在这里插入图片描述



    User对象的属性

    重要属性:

    • username:用户名
    • password:密文密码
    • is_staff:用户是否拥有网站的管理权限(是否可登陆admin后台).
    • is_active:是否允许用户登陆,设置为False后,可以在不删除用户的前提下禁止用户登陆.
      对禁用的用户调用auth.authenticate()方法将返回None

    request.user.xx :返回xx字段的值(xx可以为auth_user表中的所有字段名).
    request.user :默认返回username字段的值.



    扩展默认的auth_user表

    这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!

    比如,我想要加一个存储用户手机号的字段,怎么办?

    聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?

    答案是当然有了。

    我们可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。

    这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。

    from django.contrib.auth.models import AbstractUser
    class UserInfo(AbstractUser):
        """
        用户信息表
        """
        nid = models.AutoField(primary_key=True)
        phone = models.CharField(max_length=11, null=True, unique=True)
        
        def __str__(self):
            return self.username
    

    注意:
    按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:

    # 引用Django自带的User表,继承使用时需要设置
    AUTH_USER_MODEL = app名.UserInfo'
    

    再次注意:
    一旦我们指定了新的认证系统所使用的表,我们就需要重新在数据库中创建该表,而不能继续使用原来默认的auth_user表了。



    auth实现

    实现功能:注册用户、登陆、注销、修改密码、登陆校验

    settings.py文件增加配置项:

    # 这里配置项目登陆页面的路由
    LOGIN_URL = '/login/'
    

    urls.py文件:

    from django.conf.urls import url
    from django.contrib import admin
    from blog import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^register/$', views.register),
        url(r'^login/$', views.login),
        url(r'^index01/$', views.index01),
        url(r'^index02/$', views.index02),
        url(r'^logout/$', views.logout),
        url(r'^change_password/$', views.change_password),
        url(r'^ajax_register/$', views.ajax_register),
    ]
    

    forms.py文件:

    from django import forms
    from django.forms import Form
    from django.forms import widgets
    from django.core.exceptions import ValidationError  # 用于抛出错误信息
    
    
    # 用户登陆验证
    class LoginForm(Form):
        username = forms.CharField(
            label="用户名",
            min_length=2,
            max_length=6,
            error_messages={
                'required': "用户名不能为空",
                'invalid': "用户名格式错误",
                'min_length': "用户名最短2位",
            },  # 自定义错误提示
        )
    
        password = forms.CharField(
            label="密码",
            min_length=6,
            max_length=12,
            error_messages={
                'required': "密码不能为空",
                'invalid': "密码格式错误",
                'min_length': "密码最短6位",
            },
            widget=widgets.PasswordInput(),  # 指定input框的type类型password
        )
    
    
    # 注册验证
    class RegForm(Form):
        username = forms.CharField(
            label="用户名",
            min_length=2,
            max_length=6,
            error_messages={
                'required': "用户名不能为空",
                'invalid': "用户名格式错误",
                'min_length': "用户名最短2位",
            },  # 自定义错误提示
        )
    
        password = forms.CharField(
            label="密码",
            min_length=6,
            max_length=12,
            error_messages={
                'required': "密码不能为空",
                'invalid': "密码格式错误",
                'min_length': "密码最短6位",
            },
            widget=widgets.PasswordInput(),
        )
    
        re_password = forms.CharField(
            label="确认密码",
            min_length=6,
            max_length=12,
            error_messages={
                'required': "验证密码不能为空",
                'invalid': "密码格式错误",
                'min_length': "密码最短6位",
            },
            widget=widgets.PasswordInput(),
        )
    
        def clean_re_password(self, *args, **kwargs):
            password = self.cleaned_data.get('password')
            re_password = self.cleaned_data.get('re_password')
            if password == re_password:
                return password
            raise ValidationError("密码不一致")
    
        # 单选按钮
        level = forms.fields.ChoiceField(
            label="用户级别",
            choices=((0, "普通用户"), (1, "超级用户"),),
            initial=0,  # 默认选择普通用户
            widget=forms.widgets.RadioSelect(),
        )
    
    
    # 修改密码
    class ChangePwd(Form):
        old_password = forms.CharField(
            label="旧密码",
            min_length=6,
            max_length=12,
            error_messages={
                'required': "旧密码不能为空",
                'invalid': "密码格式错误",
                'min_length': "密码最短6位",
            },
            widget=widgets.PasswordInput(),
        )
    
        password = forms.CharField(
            label="新密码",
            min_length=6,
            max_length=12,
            error_messages={
                'required': "新密码不能为空",
                'invalid': "密码格式错误",
                'min_length': "密码最短6位",
            },
            widget=widgets.PasswordInput(),
        )
    
        re_password = forms.CharField(
            label="确认新密码",
            min_length=6,
            max_length=12,
            error_messages={
                'required': "验证密码不能为空",
                'invalid': "密码格式错误",
                'min_length': "密码最短6位",
            },
            widget=widgets.PasswordInput(),
        )
    
        def clean_re_password(self, *args, **kwargs):
            password = self.cleaned_data.get('password')
            re_password = self.cleaned_data.get('re_password')
            if password == re_password:
                return password
            raise ValidationError("密码不一致")
    

    views.py文件:

    from django.shortcuts import render, redirect, HttpResponse
    from blog.forms import *  # 导入自定义的Form组件
    from django.contrib import auth  # 导入认证模块
    from django.contrib.auth.models import User  # 导入User表
    from django.contrib.auth.decorators import login_required  # 用于验证是否登陆的装饰器
    from blog072 import settings
    
    
    # 注册
    def register(request):
        form_obj = RegForm()
        if request.method == 'POST':
            form_obj = RegForm(request.POST)
            if form_obj.is_valid():
                form_obj.cleaned_data.pop('re_password')
                level = form_obj.cleaned_data.pop('level')
                # 判断要注册的用户类型
                if not int(level):
                    # 使用create()方法创建的用户为明文密码,无法登陆(不可使用此方法)
                    # User.objects.create(**form_obj.cleaned_data)
                    # create_user():创建普通用户
                    User.objects.create_user(is_staff=1, **form_obj.cleaned_data)
                    # is_staff=1:允许登陆后台(默认不允许)
                else:
                    # create_superuser():创建超级用户
                    User.objects.create_superuser(email='', **form_obj.cleaned_data)
                return redirect('/login/')
        return render(request, 'register.html', {'form': form_obj})
    
    
    # 登陆
    def login(request):
        form_obj = LoginForm()
        if request.method == 'POST':
            form_obj = LoginForm(request.POST)
            if form_obj.is_valid():
                # 只有执行了is_valid之后,才可执行cleaned_data方法,且cleaned_data内的数据是经过校验的
                user = auth.authenticate(request, **form_obj.cleaned_data)
                # 如果校验成功,user是当前登陆用户对象,否则为None(用户被禁用[is_active=0]后,也会返回None)
                if user:
                    # 生成session数据(表名:django_session  一个浏览器对应一条session数据)
                    auth.login(request, user)
                    # 如果是跳转过来的,则登陆成功后返回至原页面:
                    next = request.GET.get('next')
                    ret = next if next else '/index01/'
                    return redirect(ret)
                print(form_obj.cleaned_data)
                return HttpResponse("用户名或密码错误")
        return render(request, 'login.html', {'form': form_obj})
    
    
    @login_required
    def index01(request):
        print("用户%s进入index01页面" % request.user)
        # request.user.xx  :xx可以为auth_user表中的所有字段名
        # request.user 默认返回username字段
        return render(request, 'index01.html')
    
    
    def index02(request):
        if not request.user.is_authenticated():
            # is_authenticated():判断当前请求是否通过了认证
            ret = '%s?next=%s' % (settings.LOGIN_URL, request.path)
            return redirect(ret)
        print("用户%s进入index02页面" % request.user)
        return render(request, 'index02.html')
    
    
    # 注销
    @login_required
    def logout(request):
        # 注销,清除session数据
        auth.logout(request)
        return redirect('/login/')
    
    
    # 修改密码
    @login_required
    def change_password(request):
        form_obj = ChangePwd()
        if request.method == 'POST':
            form_obj = ChangePwd(request.POST)
            if form_obj.is_valid():
                old_password = form_obj.cleaned_data.get('old_password')
                password = form_obj.cleaned_data.get('password')
                # 判断旧密码是否正确
                if request.user.check_password(old_password):
                    # 修改密码
                    request.user.set_password(password)
                    request.user.save()  # 同步到数据库
                    return redirect('/login/')
                return HttpResponse("旧密码错误!")
        return render(request, 'change_password.html', {'form': form_obj})
    
    
    # ajax判断用户名是否存在
    def ajax_register(request):
        username = request.GET.get('username')
        print(username)
        print(User.objects.filter(username=username).exists())
        if User.objects.filter(username=username).exists():
            return HttpResponse("0")
        return HttpResponse()
    

    HTML文件:
    注册页面(register.html):

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta http-equiv="content-Type" charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
        <title>我是注册页面</title>
    </head>
    <body>
    <form action="" method="post" novalidate>
        {% csrf_token %}
        <p id="sign01">
            {{ form.username.label }}
            {{ form.username }}
            <span style="color: red">{{ form.username.errors.0 }}</span>
            <span id="sign02" style="color: red"></span>
        </p>
        <p>
            {{ form.password.label }}
            {{ form.password }}
            <span style="color: red">{{ form.password.errors.0 }}</span>
        </p>
        <p>
            {{ form.re_password.label }}
            {{ form.re_password }}
            <span style="color: red">{{ form.re_password.errors.0 }}</span>
        </p>
        <p>
            {{ form.level.label }}
            {{ form.level }}
        </p>
        <p>
            <button>注册</button>
            <span style="font-size: 50%;">注册成功将返回登陆页面</span>
        </p>
    </form>
    {% load static %}
    <script src="{% static 'jquery-3.3.1.js' %}"></script>
    <script>
        var $UserName = $('#sign01 input');
        var $Button = $('button');
        $UserName.on('input', function () {
            $.ajax({
                url: '/ajax_register/',
                type: 'GET',
                data: {
                    username: $UserName.val(),
                    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),
                },
                success: function (data) {
                    if (data === '0') {
                        $Button.attr('disabled', 'disabled');
                        $('#sign02').text("用户名已存在");
                    } else {
                        $Button.removeAttr('disabled', 'disabled');
                        $('#sign02').text('');
                    }
                },
            });
        });
    </script>
    </body>
    </html>
    

    登陆页面(login.html):

    {# bootstrap版登陆页面 #}
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta http-equiv="content-Type" charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        {# 需要下载bootstrap样式文件 #}
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
        <title>我是登陆页面</title>
    </head>
    <body>
    <form class="form-horizontal" action="" method="post" novalidate>
        {% csrf_token %}
        <div class="form-group">
            <label for="{{ form.username.id_for_label }}" class="col-sm-2 control-label">用户名</label>
            <div class="col-sm-10">
                {{ form.username }}
                {# has-error:Form组件要想展示红色错误信息,必须要加这个类 #}
                <div class="has-error">
                    <span class="help-block">{{ form.username.errors.0 }}</span>
                </div>
            </div>
        </div>
        <div class="form-group">
            <label for="{{ form.password.id_for_label }}" class="col-sm-2 control-label">密码</label>
            <div class="col-sm-10 has-error">
                {{ form.password }}
                <div class="has-error">
                    <span class="help-block">{{ form.password.errors.0 }}</span>
                </div>
            </div>
        </div>
        <div class="form-group">
            <div class="col-sm-offset-2 col-sm-10">
                <button type="submit" class="btn btn-default">登陆</button>
                <a href="/register/" class="btn btn-sm">注册用户</a>
            </div>
        </div>
    </form>
    </body>
    </html>
    

    index01.py页面:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta http-equiv="content-Type" charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
        <title>index01</title>
    </head>
    <body>
    <h1>我是 Index01 页面</h1>
    <a href="/logout/">注销</a>
    <a href="/change_password/">修改密码</a>
    </body>
    </html>
    

    index02.py页面:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta http-equiv="content-Type" charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
        <title>index02</title>
    </head>
    <body>
    <h1>我是 Index02 页面</h1>
    <a href="/logout/">注销</a>
    <a href="/change_password/">修改密码</a>
    </body>
    </html>
    

    修改密码页面(change_password.html):

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta http-equiv="content-Type" charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.css">
        <title>我是修改密码页面</title>
    </head>
    <body>
    <form action="" method="post" novalidate>
        {% csrf_token %}
        <p>
            {{ form.old_password.label }}
            {{ form.old_password }}
            <span style="color: red">{{ form.odl_password.errors.0 }}</span>
        </p>
        <p>
            {{ form.password.label }}
            {{ form.password }}
            <span style="color: red">{{ form.password.errors.0 }}</span>
        </p>
        <p>
            {{ form.re_password.label }}
            {{ form.re_password }}
            <span style="color: red">{{ form.re_password.errors.0 }}</span>
        </p>
        <p>
            <button>提交</button>
            <span style="font-size: 50%;">修改成功后将返回登陆页面</span>
        </p>
    </form>
    </body>
    </html>
    
  • 相关阅读:
    struts2自定义拦截器之过滤不良言论---http500可能的问题所在
    bzoj4205[FJ2015集训] 卡牌配对
    bzoj1562[NOI2009] 变换序列
    bzoj1433[ZJOI2009] 假期的宿舍
    bzoj2150 部落战争
    从bzoj2463到bzoj1443和bzoj2437 博弈+二分图匹配
    bzoj4554[Tjoi2016&Heoi2016] 游戏
    bzoj1059[ZJOI2007] 矩阵游戏
    bzoj1143[CTSC2008] 祭祀river
    bzoj3175[Tjoi2013] 攻击装置
  • 原文地址:https://www.cnblogs.com/zyk01/p/10176340.html
Copyright © 2020-2023  润新知