基于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), ]
创建login视图函数

from django.shortcuts import render # Create your views here. def login(request): return render(request, 'login.html')
login.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <! 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、登录页面添加验证码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | < 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、验证码路由
1 | path( 'get_validCode_img/' , views.get_validCode_img), |
三、验证码视图函数
1、方式一:读取静态图片文件
1 2 3 | 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,该类存在于同名的模块中。可以通过以下几种方式实例化:从文件中读取图片,处理其他图片得到,或者直接创建一个图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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将图片保存在内存中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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应用实时产生各种图形。
1 2 3 4 | draw.text() 写文字 参数: xy:坐标 text:文本内容 fill:文本颜色 font:文本样式 draw.line() 画线 draw.point() 画点 |
ImageFont
模块中,可以使用 load()
函数加载一个 bitmap
字体,使用 truetype(fontfile, fontsize)
函数加载一个 OpenType/TrueType
字体(注意,这个函数需要额外安装_imageingft
模块)。
在static目录下创建font子目录,给用户存放字体文件。下载字体到该目录./cnblog/static/font/下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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字符)。
1 2 3 4 5 6 7 8 9 10 11 | # 随机字母: """ >>> chr(65) 'A' >>> chr(90) 'Z' >>> chr(97) 'a' >>> chr(122) 'z' """ |
另外还需要用到choice() 方法,该方法可返回一个列表,元组或字符串的随机项。choice()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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) |
显示效果如下所示:
四、验证码图片的噪点和噪线
添加图片噪点和噪线的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 给验证码图片添加噪点噪线 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)
函数来绘制弧线,参数解析如下所示:
1 2 3 4 5 6 | xy 是个长度为 4 的列表,用来表示一个 bounding box(边界区域)。如[x0, y0, x1, y1],分别表示 弧线最左侧距离左边、弧线最顶点距离上边、弧线最右侧距离左边、弧线最低点距离上边的距离。 start 和 end 则是弧的起止角度,单位是 °。其中水平向右的方向为 0 °,竖直向下的方向为 90 °,水平向左的方向为 180 °,竖直向上的方向为 270 °。 options 中可用选项: fill = (R, G, B) :指定线条颜色 |
验证码视图修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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"
1 2 3 4 5 6 7 8 9 10 11 12 | <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:
1 | < script src="/static/js/jquery-3.3.1.js"></ script > |
(3)在页面控制台操作验证码图片:
每次在$("#valid_code_img")[0].src后面添加一个“?”都会刷新验证码图片。
2、实现点击验证码,验证码刷新
1 2 3 4 5 6 7 | <script src= "/static/js/jquery-3.3.1.js" ></script> <script> // 刷新验证码 $( "#valid_code_img" ).click( function () { $( this )[0].src+= "?" }) </script> |
六、验证验证码字符串
1、给btn绑定ajax事件:登录验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 登录验证 $( ".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中找一个地方加入:
1 | {% csrf_token %} |
但是光加这个是不能通过校验的,这里与发form请求不同,需要自己组csrf_token键值:
可以在这里看到键名:csrfmiddlewaretoken,利用键名组csrf键值对。
1 2 | // 自己组csrf键值 csrfmiddlewaretoken: $( "[name='csrfmiddlewaretoken']" ).val(), { # csrf_token的值 #} |
2、login视图函数处理POST请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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视图函数中需要添加如下代码:
1 2 3 4 5 6 7 | 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()方法:
1 | if valid_code.upper() = = valid_code_str.upper(): # 添加upper()不区分大小写 |
(5)由于ajax一般都需要return 一个响应字符串,在这里引入JsonResponse:
1 | from django.http import JsonResponse |
字典放进去直接序列化,ajax拿到的就是对象,两边都不需要进行json的序列化与反序列化。
验证码验证成功,django_session表保存浏览器对应ssession记录:
七、登录验证
1、引入用户认证组件auth模块
1 | from django.contrib import auth |
2、在验证码验证通过后,运用authenticate()方法完成用户认证,即验证用户名以及密码是否正确
1 | user = auth.authenticate(username = user, password = pwd) |
3、添加一个用户,在控制台执行如下命令
1 | $ python3 manage.py createsuperuser |
4、验证用户信息无误后,使用login函数给使用django的session框架给某个已认证的用户附加上session id等信息
1 | auth.login(request, user) # request.user:当前登录对象 |
request.user是全局变量,在任何视图和模板中可以直接使用。
5、运用ajax,在页面显示报错信息
(1)修改提交按钮的input标签样式,并在后面加span标签
1 | < input type = "button" class = "btn btn-default login-btn" value = "提交" ><span class = "error" >< / span> |
(2)编写ajax请求回调函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 登录验证 $( ".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路由和视图
1 | path( 'index/' , views.index), |
视图:
1 2 3 | def index(request): return render(request, "index.html" ) |
index模板:
1 2 3 4 5 6 7 8 9 10 | <! DOCTYPE html> < html lang="en"> < head > < meta charset="UTF-8"> < title >Title</ title > </ head > < body > < h3 >首页{{ request.user.username }}</ h3 > </ body > </ html > |
(4)效果图如下所示:
八、登录验证优化
1、在登录验证错误时,显示的错误提示一秒后自动消失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 登录验证 $( ".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,将验证码功能相关代码拷入该文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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)在视图函数中引入该模块,实现验证码功能
1 2 3 4 5 6 7 8 9 10 | 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、验证码刷新
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术