• 基于Ajax与用户认证系统的登录验证


    一、登录页面

    from django.contrib import admin
    from django.urls import path
    from blog import views
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('login/', views.login),
    ]
    urls.py

      创建login视图函数

    from django.shortcuts import render
    
    # Create your views here.
    
    
    def login(request):
        return render(request, 'login.html')
    views.py

      login.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页面</title>
        <link rel="stylesheet" href="/static/blog/bootstrap-3.3.7/css/bootstrap.css">
    </head>
    <body>
    <h3>登录页面</h3>
    <div class="container">
        <div class="row">
            <div class="col-md-6 col-lg-offset-3">
                <form action="">
                    <div class="form-group">
                        {# label标签的"for"属性可把label绑定到另外一个元素,因此要把for属性值与input的id属性相同。#}
                        {# 当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件 #}
                        <label for="user">用户名</label>
                        {# 这里没必要加name属性了,之前加是点击submit按钮自己组装键值发出去,现在用ajax发只要找到标签拿到里面的值即可 #}
                        <input type="text" id="user" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="pwd">密码</label>
                        <input type="password" id="pwd" class="form-control">
                    </div>
                    {# 这里提交按钮不能使用<input type="submit">这就变成form表单提交事件了。 button类型时,这个按钮没有任何事件,可以给这个按钮绑定一个事件 #}
                    <input type="button" class="btn btn-default login-btn pull-right" value="提交">
                </form>
            </div>
        </div>
    </div>
    </body>
    </html>
    

    注意:

      1、label标签的"for"属性可把label绑定到另外一个元素,因此要把for属性值与input的id属性相同。当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件;

      2、这里没必要加name属性了,之前加是点击submit按钮自己组装键值发出去,现在用ajax发只要找到标签拿到里面的值即可。

      3、这里提交按钮不能使用<input type="submit">这样就变成form表单提交事件了。 设为button类型时,这个按钮没有任何事件,可以给这个按钮绑定一个事件

    二、在页面中添加验证码图片请求路径

    1、登录页面添加验证码

    <div class="container">
        <div class="row">
            <div class="col-md-6 col-lg-offset-3">
                <form action="">
                    <div class="form-group">
                        <label for="user">用户名</label>
                        <input type="text" id="user" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="pwd">密码</label>
                        <input type="password" id="pwd" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="pwd">验证码</label>
                        <div class="row">
                            <div class="col-md-6">
                                <input type="text" class="valid_code form-control">
                            </div>
                            <div class="col-md-6">
                                {# src还可以设置请求路径 #}
                                <img width="270" height="40" src="/get_validCode_img/" alt="">
                            </div>
                        </div>
                    </div>
                    <input type="button" class="btn btn-default login-btn pull-right" value="提交">
                </form>
            </div>
        </div>
    </div>
    

      注意:<img src="">,src除了可以指定图片路径还是设置为请求路径。

    2、验证码路由

    path('get_validCode_img/', views.get_validCode_img),
    

    三、验证码视图函数

    1、方式一:读取静态图片文件

    def get_validCode_img(request):
        with open("lufei.jpg", "rb") as f:
            data = f.read()
    

      显示效果如下:

      

      不推荐使用这种方法,验证图片不能仅仅指定一张图片,这种方法把程序写死了。

    2、方式二:pillow生成动态图片

      生成动态随机图片,使用Python图像处理库:Pillow

      安装pillow库:pip3 install pillow

      引入Pillow中最重要的类Image,该类存在于同名的模块中。可以通过以下几种方式实例化:从文件中读取图片,处理其他图片得到,或者直接创建一个图片。

    import random
    def get_validCode_img(request):
    
        def get_random_color():
            return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
    
        # 方式二:pip3 install pillow
        from PIL import Image
        img = Image.new("RGB", (270, 40), color=get_random_color())  # 得到img对象,颜色三要素:红绿蓝
    
        with open("validCode.png", "wb") as f:
            img.save(f, "png")    # 保存动态生成的图片
    
        with open("validCode.png", "rb") as f:
            data = f.read()
    
        return HttpResponse(data)
    

      运行效果如下:

      

      每次刷新,随机图片的颜色会发生随机变换。但是这种方式是请求进来时,先把数据加载到磁盘上,再在磁盘把数据读出来返还给浏览器,而且磁盘的处理数据时非常慢的,因此应该交到内存中管理。

    3、方式三:使用pillow生成动态页面的基础上,引入BytesIO将图片保存在内存中

    import random
    def get_validCode_img(request):
    
        def get_random_color():
            return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
    
        # 方式三:要引入BytesIO
        from PIL import Image
        from io import BytesIO
        img = Image.new("RGB", (270, 40), color=get_random_color())  # 得到img对象,颜色三要素:红绿蓝
    
        # f为内存句柄
        f= BytesIO()    # 会自己处理内存回收
        # 保存图片
        img.save(f, "png")
        data = f.getvalue()
    
        return HttpResponse(data)
    

    4、方式四:添加验证码文字信息

      ImageDraw模块提供了图像对象的简单2D绘制。用户可以使用这个模块创建新的图像,注释或润饰已存在图像,为web应用实时产生各种图形。

    draw.text()   写文字
            参数:  xy:坐标   text:文本内容   fill:文本颜色  font:文本样式
    draw.line()   画线
    draw.point()  画点

      ImageFont 模块中,可以使用 load() 函数加载一个 bitmap 字体,使用 truetype(fontfile, fontsize) 函数加载一个 OpenType/TrueType 字体(注意,这个函数需要额外安装_imageingft模块)。

      在static目录下创建font子目录,给用户存放字体文件。下载字体到该目录./cnblog/static/font/下。

    import random
    def get_validCode_img(request):
    
        # 随机颜色
        def get_random_color():
            return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
    
        # 方式四:
        from PIL import Image, ImageDraw,ImageFont
        from io import BytesIO
    
        img = Image.new("RGB", (270, 40), color=get_random_color())  # 得到img对象,颜色三要素:红绿蓝
        # 创建Draw对象
        draw = ImageDraw.Draw(img)
        # 创建Font对象
        kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=20)
        draw.text((0,5), "python", get_random_color(), font=kumo_font)
        f = BytesIO()   # f为内存句柄
        img.save(f, "png")
        data = f.getvalue()
    
        return HttpResponse(data)
    

      显示效果如下所示:

      

    5、生成随机字符串验证码

      随机生成大写字母、小写字母、数字。

      针对随机字母需要用到chr()方法,用一个范围在 range(256)内的(就是0~255)整数作参数,返回一个对应的字符(当前整数对应的ascii字符)。

    # 随机字母:
    """
    >>> chr(65)
    'A'
    >>> chr(90)
    'Z'
    >>> chr(97)
    'a'
    >>> chr(122)
    'z'
    """
    

      另外还需要用到choice() 方法,该方法可返回一个列表,元组或字符串的随机项。choice()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。

    import random
    def get_validCode_img(request):
    
        # 随机颜色
        def get_random_color():
            return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
    
        # 方式五:修改为随机字符串
        from PIL import Image, ImageDraw, ImageFont
        from io import BytesIO
    
        img = Image.new("RGB", (270, 40), color=get_random_color())  # 得到img对象,颜色三要素:红绿蓝
        # 创建Draw对象
        draw = ImageDraw.Draw(img)
        # 创建Font对象
        kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=28)
    
        for i in range(5):
            random_num = str(random.randint(0, 9))     # 随机数字
            random_low_alpha = chr(random.randint(95, 122))   # 随机小写字母
            random_upper_alpha = chr(random.randint(65, 90))   # 随机大写字母
    
            # 三选一:choice() 方法返回一个列表,元组或字符串的随机项。
            random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
            draw.text((i*50+20, 5), random_char, get_random_color(), font=kumo_font)  # 坐标错开间距
    
        f = BytesIO()  # f为内存句柄
        img.save(f, "png")
        data = f.getvalue()
    
        return HttpResponse(data)
    

      显示效果如下所示:

      

    四、验证码图片的噪点和噪线

      添加图片噪点和噪线的代码:

    # 给验证码图片添加噪点噪线
    width = 270
    height = 40
    for i in range(10):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        draw.line((x1, y1, x2, y2), fill = get_random_color())   # 画出一条线
    
    for i in range(50):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())   # 画点
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y +4), 0, 90, fill=get_random_color())
    

      可以任意调配噪线和噪点的数量,尽量保证机器无法识别,但人可以识别。

      Draw 类提供了 arc(xy, start, end, options) 函数来绘制弧线,参数解析如下所示:

    xy 是个长度为4的列表,用来表示一个 bounding box(边界区域)。如[x0, y0, x1, y1],分别表示 弧线最左侧距离左边、弧线最顶点距离上边、弧线最右侧距离左边、弧线最低点距离上边的距离。
    
    start 和 end 则是弧的起止角度,单位是 °。其中水平向右的方向为 0°,竖直向下的方向为 90°,水平向左的方向为 180°,竖直向上的方向为 270°。
    
    options 中可用选项:
        fill = (R, G, B) :指定线条颜色

      验证码视图修改如下:

    import random
    def get_validCode_img(request):
    
        # 随机颜色
        def get_random_color():
            return (random.randint(0,255), random.randint(0, 255), random.randint(0, 255))
    
        from PIL import Image, ImageDraw, ImageFont
        from io import BytesIO
    
        img = Image.new("RGB", (270, 40), color=get_random_color())  # 得到img对象,颜色三要素:红绿蓝
        # 创建Draw对象
        draw = ImageDraw.Draw(img)
        # 创建Font对象
        kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=28)
            for i in range(5):
            random_num = str(random.randint(0, 9))     # 随机数字
            random_low_alpha = chr(random.randint(95, 122))   # 随机小写字母
            random_upper_alpha = chr(random.randint(65, 90))   # 随机大写字母
    
            # 三选一:choice() 方法返回一个列表,元组或字符串的随机项。注意:choice()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
            random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
            draw.text((i*50+20, 5), random_char, get_random_color(), font=kumo_font)  # 坐标错开间距
    
        # 给验证码图片添加噪点噪线
        width = 270
        height = 40
        for i in range(10):
            x1 = random.randint(0, width)
            x2 = random.randint(0, width)
            y1 = random.randint(0, height)
            y2 = random.randint(0, height)
            draw.line((x1, y1, x2, y2), fill = get_random_color())   # 画出一条线
    
        for i in range(50):
            draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())   # 画点
            x = random.randint(0, width)
            y = random.randint(0, height)
            draw.arc((x, y, x + 4, y +4), 0, 90, fill=get_random_color())
    
        f = BytesIO()  # f为内存句柄
        img.save(f, "png")
        data = f.getvalue()
    
        return HttpResponse(data)
    

      显示效果如下所示:

      

    五、验证码刷新功能

    1、验证码图片刷新原理 

    (1)给验证码图片添加id属性:id="valid_code_img"

    <div class="form-group">
        <label for="pwd">验证码</label>
        <div class="row">
            <div class="col-md-6">
                <input type="text" class="valid_code form-control">
            </div>
            <div class="col-md-6">
                {# src还可以设置请求路径 #}
                <img width="270" height="40" id="valid_code_img" src="/get_validCode_img/" alt="">
            </div>
        </div>
    </div>
    

    (2)创建/static/js/目录,添加jquery-3.3.1.js,在login.html中引入jquery:

    <script src="/static/js/jquery-3.3.1.js"></script>
    

    (3)在页面控制台操作验证码图片:

      

      每次在$("#valid_code_img")[0].src后面添加一个“?”都会刷新验证码图片。

    2、实现点击验证码,验证码刷新

    <script src="/static/js/jquery-3.3.1.js"></script>
    <script>
        // 刷新验证码
        $("#valid_code_img").click(function () {
            $(this)[0].src+="?"
        })
    </script>

     六、验证验证码字符串

    1、给btn绑定ajax事件:登录验证

    // 登录验证
    $(".login-btn").click(function () {
        $.ajax({
            url: "",
            type: "post",
            data: {
                user: $("#user").val(),
                pwd: $("#pwd").val(),
                valid_code: $("#valid_code").val(),
                // 自己组csrf键值
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),   {# csrf_token的值 #}
            },
            success:function (data) {
                console.log(data)
            }
        })
    })
    

      给后端提交用户名密码、验证码做校验。

      注意:发post请求一定要通过csrf校验,因此要在form中找一个地方加入:

    {% csrf_token %}
    

      但是光加这个是不能通过校验的,这里与发form请求不同,需要自己组csrf_token键值:

      

      可以在这里看到键名:csrfmiddlewaretoken,利用键名组csrf键值对。

    // 自己组csrf键值
    csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),   {# csrf_token的值 #}
    

    2、login视图函数处理POST请求

    from django.http import JsonResponse
    
    def login(request):
        if request.method == "POST":
            response = {"user": None, "msg": None}
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            valid_code = request.POST.get("valid_code")
            # 从session中取到值,一个浏览器存一份,不会发生相互干扰
            valid_code_str = request.session.get("valid_code_str")
    
            if valid_code.upper() == valid_code_str.upper():   # 添加upper()不区分大小写
                pass
            else:
                response['msg'] = "valid code error!"
            return JsonResponse(response)   # 字典放进去直接序列化,ajax拿到的就是  格式,不用反序列化了
    
        return render(request, 'login.html')
    

      (1)在校验用户名密码前,要先校验验证码。

      (2)注意这里做验证码校验,但是验证码在另一个视图函数中,要取到另一个函数的验证码,不能设置为全局变量,这样不同人登录时会互相干扰,校验无法保证正常完成。

      因为session本身就是一个会话跟踪,能够保存上一次做的行为、操作、数据,因此利用它能完成验证码验证

      (3)在get_validcode_img视图函数中需要添加如下代码:

    request.session["valid_code_str"] = valid_code_str
        """验证码生成过程
        1 生成一个随机字符串
        2 设置一个COOKIE,{"sessionid":"刚刚生成的随机字符串"}
        3 django-session表中存储 session-key     session-data
                                随机字符串       {"valid_code_str": "随机验证码字符"}
        """
    

      (4)另外由于验证码校验是不区分大小写的,在login中校验验证码时,添加upper()方法:

    if valid_code.upper() == valid_code_str.upper():   # 添加upper()不区分大小写
    

      (5)由于ajax一般都需要return 一个响应字符串,在这里引入JsonResponse:

    from django.http import JsonResponse
    

      字典放进去直接序列化,ajax拿到的就是对象,两边都不需要进行json的序列化与反序列化。

      

      验证码验证成功,django_session表保存浏览器对应ssession记录:

      

    七、登录验证

    1、引入用户认证组件auth模块

    from django.contrib import auth
    

    2、在验证码验证通过后,运用authenticate()方法完成用户认证,即验证用户名以及密码是否正确

    user = auth.authenticate(username=user, password=pwd)
    

    3、添加一个用户,在控制台执行如下命令

    $ python3 manage.py createsuperuser
    

    4、验证用户信息无误后,使用login函数给使用django的session框架给某个已认证的用户附加上session id等信息

    auth.login(request, user)   # request.user:当前登录对象
    

      request.user是全局变量,在任何视图和模板中可以直接使用

    5、运用ajax,在页面显示报错信息

    (1)修改提交按钮的input标签样式,并在后面加span标签

    <input type="button" class="btn btn-default login-btn" value="提交"><span class="error"></span>
    

    (2)编写ajax请求回调函数:

    // 登录验证
    $(".login-btn").click(function () {
        $.ajax({
            url: "",
            type: "post",
            data: {
                user: $("#user").val(),
                pwd: $("#pwd").val(),
                valid_code: $("#valid_code").val(),
                // 自己组csrf键值
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),   {# csrf_token的值 #}
            },
            success:function (data) {
                console.log(data);
                if (data.user){
                    // 如果有值:前端跳转
                    location.href = "/index/"
                } else {
                    // 如果没值
                    $(".error").text(data.msg).css({"color": "red", "margin-left": "10px"})
                }
            }
        })
    })
    

      注意:前端跳转写法和错误信息样式修改方式。

    (3)添加index路由和视图

    path('index/', views.index),
    

      视图:

    def index(request):
    
        return render(request, "index.html")
    

      index模板:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h3>首页{{ request.user.username }}</h3>
    </body>
    </html>
    

    (4)效果图如下所示:

       

    八、登录验证优化

    1、在登录验证错误时,显示的错误提示一秒后自动消失

    // 登录验证
    $(".login-btn").click(function () {
        $.ajax({
            url: "",
            type: "post",
            data: {
                user: $("#user").val(),
                pwd: $("#pwd").val(),
                valid_code: $("#valid_code").val(),
                // 自己组csrf键值
                csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),   {# csrf_token的值 #}
            },
            success:function (data) {
                console.log(data);
                if (data.user){
                    // 如果有值:前端跳转
                    location.href = "/index/"
                } else {
                    // 如果没值
                    $(".error").text(data.msg).css({"color": "red", "margin-left": "10px"})
                    setTimeout(function () {
                        $(".error").text("");   // 一秒后清空错误提示
                    }, 1000)
                }
            }
        })
    })
    

      这里主要是用到了javascript中的setTimeout()方法,用来设定一个时间, 时间到了, 就会执行一个指定的 method。

    2、从视图中分离出验证码功能代码

      验证码功能代码非常多且逻辑复杂,将这一部分逻辑构建为一个模块,视图中调用这个模块,实现程序解耦:

    (1)创建./blog/utils/目录,创建文件validCode.py,将验证码功能相关代码拷入该文件中:

    import random
    
    def get_random_color():
        # 随机颜色
        return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    
    def get_valid_code_imge(request):
        from PIL import Image, ImageDraw, ImageFont
        from io import BytesIO
    
        img = Image.new("RGB", (270, 40), color=get_random_color())  # 得到img对象,颜色三要素:红绿蓝
        # 创建Draw对象
        draw = ImageDraw.Draw(img)
        # 创建Font对象
        kumo_font = ImageFont.truetype("static/font/kumo.ttf", size=28)
    
        valid_code_str = ""
    
        for i in range(5):
            random_num = str(random.randint(0, 9))  # 随机数字
            random_low_alpha = chr(random.randint(95, 122))  # 随机小写字母
            random_upper_alpha = chr(random.randint(65, 90))  # 随机大写字母
    
            # 三选一:choice() 方法返回一个列表,元组或字符串的随机项。
            random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
            draw.text((i * 50 + 20, 5), random_char, get_random_color(), font=kumo_font)  # 坐标错开间距
    
            # 保存验证码字符串
            valid_code_str += random_char
    
        # 给验证码图片添加噪点噪线
        # width = 270
        # height = 40
        # for i in range(10):
        #     x1 = random.randint(0, width)
        #     x2 = random.randint(0, width)
        #     y1 = random.randint(0, height)
        #     y2 = random.randint(0, height)
        #     draw.line((x1, y1, x2, y2), fill = get_random_color())   # 画出一条线
        #
        # for i in range(50):
        #     draw.point([random.randint(0, width), random.randint(0, height)], fill=get_random_color())   # 画点
        #     x = random.randint(0, width)
        #     y = random.randint(0, height)
        #     draw.arc((x, y, x + 4, y +4), 0, 90, fill=get_random_color())
    
        print("valid_code_str", valid_code_str)  # valid_code_str Ms4v0
    
        # 为什么用request.session:
        # 因为session本身就是一个会话跟踪,能够保存上一次做的行为、操作、数据,因此利用它能完成验证码验证
        request.session["valid_code_str"] = valid_code_str
    
        f = BytesIO()  # f为内存句柄
        img.save(f, "png")
        data = f.getvalue()
    
        return data
    

    (2)在视图函数中引入该模块,实现验证码功能

    def get_validCode_img(request):
        """
        基于PIL模块动态生成响应状态码图片
        :param request:
        :return:
        """
        from blog.utils.validCode import get_valid_code_imge
        data = get_valid_code_imge(request)
    
        return HttpResponse(data)

    九、总结登录验证重点

    1、一次请求伴随了多次请求(伴随了多个静态文件的请求)

    2、PIL模块掌握,验证码

    3、session存储

    4、验证码刷新

  • 相关阅读:
    【自学php】第三天
    【自学php】第二天
    【自学php】第一天-macbook上配置php
    js数值转换
    js题
    【练习】响应式布局
    6.数据查询
    5.删除数据
    4.更新数据
    3.插入数据
  • 原文地址:https://www.cnblogs.com/xiugeng/p/9346269.html
Copyright © 2020-2023  润新知