Django之验证码功能

发布于:2025-05-21 ⋅ 阅读:(25) ⋅ 点赞:(0)

验证码功能

目录

1.绘制验证码

2.在登录页面里面实现验证码的功能

3.代码展示集合

这篇文章, 内容不是很多, 不过验证码, 是在网页里面比较常见的功能, 所有我们还是要掌握它!!!

一、绘制验证码

绘制验证码, 我们需要用到图像, 然后在图片里面插入要输入的验证码的文字和文字周边的一些干扰线条或点。

我们在utils文件夹下面, 新建一个code.py文件, 这个文件里面写的代码, 就是生成验证码的代码。

code.py:

from PIL import Image, ImageDraw, ImageFont
from random import randint, choice


def create_image_content():
    # 创建画布
    # mode='RGB'代表使用RGB, size代表画布大小, color代表颜色, 用的rgb颜色。
    image = Image.new(mode='RGB', size=(110, 40), color=(255, 255, 255))
    # 创建画笔
    draw = ImageDraw.Draw(image, mode='RGB')
    font = ImageFont.truetype('simhei.ttf', size=30)
    # 验证码的内容, 都是从text字符串的范围之内生成的
    text = "ABCDEFG123456"
    # image_text是用来存储生成的验证码的内容
    image_text = ""
    for i in range(4):
        # 在text里面随机挑选一个元素然后添加到image_text里面
        image_text += choice(text)

    # 在画布(图像)上画四个文字, 这四个文字就是我们生成的验证码的内容。
    x = 17
    for i in image_text:
        R = str(randint(0, 255))
        G = str(randint(0, 255))
        B = str(randint(0, 255))
        draw.text((x, 5), text=i, fill=f"rgb({R}, {G}, {B})", font=font)
        x += 20
	
    # 在画布(图像)上面增加4到5条干扰线。
    for i in range(1, randint(4, 6)):
        x1, y1 = randint(0, 110), randint(0, 40)
        x2, y2 = randint(0, 110), randint(0, 40)
        R = str(randint(0, 255))
        G = str(randint(0, 255))
        B = str(randint(0, 255))
        draw.line((x1, y1, x2, y2), fill=f"rgb({R}, {G}, {B})", width=2)
	
    # image.save('code.png') 验证码生成后, 把图片保存下来。
    # 返回画布(图片)和验证码图片里面的内容
    return [image, image_text]
# 调用函数
# create_image_content()

运行结果:

在这里插入图片描述

在相同的路径下, 会出现code.png, 这个就是我们生成的验证码的图片。

注意: 如果要运行此文件, 需要把我已经注释的image.save(‘code.png’)和create_image_content()给它去掉。然后再运行才会有结果。给它注释掉的原因是之后我们用到自己写的创建验证码的功能的时候, 需要把image.save(‘code.png’)和create_image_content()注释掉。

打开code.png:

在这里插入图片描述

验证码生成成功!!!

二、在登录页面里面实现验证码的功能

我们打开login.py, 写验证码生成的函数:

login.py:

def image_code(request):
    image, text = create_image_content()
    request.session['image_code'] = text
    request.session.set_expiry(60)
    stream = BytesIO()
    image.save(stream, 'png')
    return HttpResponse(stream.getvalue())

这个就是生成验证码的函数。

然后, 我们给它配置路由:

urls.py:

"""project_simple URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from project_one.views import depart, user, assets, admin_role, login

urlpatterns = [
    # path('admin/', admin.site.urls),
    path("", depart.index, name="index"),
    path("depart/", depart.depart, name="depart"),
    path("depart/add/", depart.add_depart, name="add_depart"),
    path("depart/<int:nid>/modify/", depart.depart_modify, name="depart_modify"),
    path("depart/<int:nid>/del/", depart.del_depart, name="del_depart"),
    path("user/", user.user_info, name="user_info"),
    path("user/add/", user.user_add, name="user_add"),
    path("user/<int:nid>/modify/", user.user_modify, name="user_modify"),
    path("user/<int:nid>/del/", user.user_del, name="user_del"),
    path("user/add/modelform", user.user_add_modelform, name="user_add_modelform"),
    path("user/<int:nid>/modify/modelform", user.user_modify_modelform, name="user_modify_modelform"),
    path("assets_list/", assets.assets, name="assets"),
    path("assets/add/", assets.assets_add, name="assets_add"),
    path("assets/<int:nid>/modify/", assets.assets_modify, name="assets_modify"),
    path("assets/<int:nid>/del/", assets.assets_del, name="assets_del"),
    path("admin_list/", admin_role.admin, name="admin"),
    path("admin/add/", admin_role.admin_add, name="admin_add"),
    path("admin/<int:nid>/modify/", admin_role.admin_modify, name="admin_modify"),
    path("admin/<int:nid>/reset/pwd/", admin_role.admin_reset_pwd, name="admin_reset_pwd"),
    path("admin/<int:nid>/del/", admin_role.admin_del, name="admin_del"),
    path("login/", login.login, name="login"),
    path("logout/", login.logout, name="logout"),
    # 生成验证码功能的路由
    path("image/code/", login.image_code, name="image_code")
]

然后我们回到login.py文件, 在上次写的login函数里面的表单验证那边, 写上验证码的判断, 就是上次写的账号密码登录的判断的那个地方。

在这里插入图片描述

我们在自定义的form类里面加上验证码的输入框:

class LoginForm(forms.Form):
    username = forms.CharField(label="用户名", widget=forms.TextInput(attrs={"placeholder": "用户名", "autocomplete": "off"}))
    password = forms.CharField(label="密码", widget=forms.PasswordInput(attrs={"placeholder": "密码", "autocomplete": "off", "type": "password"}))
    # 验证码输入框
    code = forms.CharField(label="验证码", widget=forms.TextInput(attrs={"placeholder": "验证码", "autocomplete": "off"}))

    # 对密码进行校验, 在校验函数里面, 我们对密码进行加密处理
    def clean_password(self):
        password = self.cleaned_data['password']
        return pwd_data.md5(password)

然后再login函数写关于验证码判断的逻辑:

def login(request):
    if request.method == 'GET':
        form = LoginForm()
        return render(request, "login/login.html", {'form': form})
    form = LoginForm(request.POST)
    if form.is_valid():
        # 验证码校验(这一篇文章写的)
        # 用户输入的验证码
        user_input_code = form.cleaned_data.pop("code")
        # 图片生成的验证码
        code = str(request.session.get("image_code"))
        # 如果验证码不一致(由于验证码不区分大小写, 所以两边都加了upper函数, 或者都加上lower函数也可以)
        if user_input_code.upper() != code.upper():
            # 添加报错信息
            form.add_error("code", "验证码错误")
            return render(request, "login/login.html", {'form': form})
        print(form.cleaned_data)
        # 登录校验(上一篇文章写的)
        admin_object = models.AdminRole.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            form.add_error("password", "账号或密码错误")
            return render(request, "login/login.html", {'form': form})
        # 如果用户名和密码正确,即可登陆成功,将用户名和密码,身份信息存储在session当中
        request.session['info'] = {"id": admin_object.id, "username": admin_object.username, "password": admin_object.password, "role": admin_object.role}
        # 设置账号的时效期, 这里以秒为单位, 我们设置一个账号, 登录以后, 可以有一天时间使用, 时效期过去之后需要重新登录才可以继续使用网页
        request.session.set_expiry(60*60*24*1)
        return redirect('/')
    render(request, "login/login.html", {'form': form})

然后我们不要忘记在中间件里面, 把我们写好的生成验证码的路由(“/image/code/”)添加到判断里面:

auth.py:

from django.shortcuts import render, redirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings


class AuthMiddleware(MiddlewareMixin):
    # 登录校验
    def process_request(self, request):
        # 避免打开login页面之后验证login页面, 不然就会产生死循环。
        # 这里需要忽略以下路由
        # 这里千万不要忘记写"/image/code/"
        if request.path_info in ["/login/", "/logout/", "/image/code/"]:
            return
        info_dict = request.session.get('info')
        if info_dict:
            request.unicom_id = info_dict['id']
            request.unicom_username = info_dict['username']
            request.unicom_role = info_dict['role']
            return
        return redirect("/login/")

    def process_view(self, request, view_func, args, kwargs):
        # 这里千万不要忘记写"/image/code/"
        if request.path_info in ["/login/", "/logout/", "/image/code/"]:
            return
        role = request.unicom_role
        user_permission_list = settings.UNICOM_PERMISSION[role]

        # 当前请求的路由name不在这个列表当中
        if request.resolver_match.url_name not in user_permission_list:
            return
        return HttpResponse("没有权限")

最后我们在前端加上验证码的输入框:

login.html:

<div class="item">
    {# 验证码输入框 #}
    {{ form.code }}
    {# 验证码报错信息展示(无报错就不会展示) #}
    <span style="color: red">{{ form.code.errors.0 }}</span>
    {# 验证码展示区域, 把它放在右边 #}
    <button style="border: none; float: right">
        {# 验证码图片的生成(src里面是生成验证码的路由, 刚在我们已经配置好了) #}
        <img src="/image/code/">
    </button>
</div>

运行结果:

在这里插入图片描述

我们随便登录一个账号试一试:

在这里插入图片描述

验证码是CC2F, 但是输入框里面是CCs2, 那就会有错误信息:

在这里插入图片描述

有验证码错误的报错信息,

并且会自动重新生成一个验证码。

当我们输入正确的验证码的时候:

在这里插入图片描述

再点击登录, 会跳转到首页。(这个验证码错误不需要管它, 重新输入验证码的时候, 那个文字不会消失, 输入正确的验证码之后, 页面就会自动跳转到首页)。

不过有个前提, 就是账号密码必须要正确的情况下才能登录成功, 所以是账号密码要正确, 验证码也要正确。

在这里插入图片描述

登录成功!!!

三、代码展示集合

前端:

login.html:

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		* {
			margin: 0;
			padding: 0;
		}

		a {
			text-decoration: none;
		}

		input,
		button {
			background: transparent;
			border: 0;
			outline: none;
		}

		body {
			height: 100vh;
			background: linear-gradient(#141e30, #243b55);
			display: flex;
			justify-content: center;
			align-items: center;
			font-size: 16px;
			color: #03e9f4;
		}

		.loginBox {
			width: 400px;
			height: 464px;
			background-color: #0c1622;
			margin: 100px auto;
			border-radius: 10px;
			box-shadow: 0 15px 25px 0 rgba(0, 0, 0, .6);
			padding: 40px;
			box-sizing: border-box;
		}

		h2 {
			text-align: center;
			color: aliceblue;
			margin-bottom: 30px;
			font-family: 'Courier New', Courier, monospace;
		}

		.item {
			height: 45px;
			border-bottom: 1px solid #fff;
			margin-bottom: 40px;
			position: relative;
		}

		.item input {
			width: 100%;
			height: 100%;
			color: #fff;
			padding-top: 20px;
			box-sizing: border-box;
		}

		.item input:focus+label,
		.item input:valid+label {
			top: 0px;
			font-size: 2px;
		}

		.item label {
			position: absolute;
			left: 0;
			top: 12px;
			transition: all 0.5s linear;
		}

		.btn {
			padding: 10px 20px;
			margin-top: 30px;
			color: #03e9f4;
			position: relative;
			overflow: hidden;
			text-transform: uppercase;
			letter-spacing: 2px;
			left: 35%;
		}

		.btn:hover {
			border-radius: 5px;
			color: #fff;
			background: #03e9f4;
			box-shadow: 0 0 5px 0 #03e9f4,
				0 0 25px 0 #03e9f4,
				0 0 50px 0 #03e9f4,
				0 0 100px 0 #03e9f4;
			transition: all 1s linear;
		}

		.btn>span {
			position: absolute;
		}

		.btn>span:nth-child(1) {
			width: 100%;
			height: 2px;
			background: -webkit-linear-gradient(to left, transparent, #03e9f4);
			left: -100%;
			top: 0px;
			animation: line1 1s linear infinite;
		}

		@keyframes line1 {

			50%,
			100% {
				left: 100%;
			}
		}

		.btn>span:nth-child(2) {
			width: 2px;
			height: 100%;
			background: -webkit-linear-gradient(to top, transparent, #03e9f4);
			right: 0px;
			top: -100%;
			animation: line2 1s 0.25s linear infinite;
		}

		@keyframes line2 {

			50%,
			100% {
				top: 100%;
			}
		}

		.btn>span:nth-child(3) {
			width: 100%;
			height: 2px;
			background: -webkit-linear-gradient(to left, #03e9f4, transparent);
			left: 100%;
			bottom: 0px;
			animation: line3 1s 0.75s linear infinite;
		}

		@keyframes line3 {

			50%,
			100% {
				left: -100%;
			}
		}

		.btn>span:nth-child(4) {
			width: 2px;
			height: 100%;
			background: -webkit-linear-gradient(to top, transparent, #03e9f4);
			left: 0px;
			top: 100%;
			animation: line4 1s 1s linear infinite;
		}

		@keyframes line4 {

			50%,
			100% {
				top: -100%;
			}
		}
	</style>
</head>

<body>
	<div class="loginBox">
		<h2>登录界面</h2>
		<form method="post">
            {% csrf_token %}
            <div class="item">
                {{ form.username }}
            </div>
            <div class="item">
                {{ form.password }}
                <span style="color: red">{{ form.password.errors.0 }}</span>
            </div>
            <div class="item">
                {{ form.code }}
                <span style="color: red">{{ form.code.errors.0 }}</span>
                <button style="border: none; float: right">
                    <img src="/image/code/">
                </button>
            </div>
			<button class="btn">登录
				<span></span>
				<span></span>
				<span></span>
				<span></span>
			</button>
		</form>
	</div>
</body>
</html>
后端:

code.py:

from PIL import Image, ImageDraw, ImageFont
from random import randint, choice


def create_image_content():
    # 创建画布
    # mode='RGB'代表使用RGB, size代表画布大小, color代表颜色, 用的rgb颜色。
    image = Image.new(mode='RGB', size=(110, 40), color=(255, 255, 255))
    # 创建画笔
    draw = ImageDraw.Draw(image, mode='RGB')
    font = ImageFont.truetype('simhei.ttf', size=30)
    text = "ABCDEFG123456"
    image_text = ""
    for i in range(4):
        image_text += choice(text)

    x = 17
    for i in image_text:
        R = str(randint(0, 255))
        G = str(randint(0, 255))
        B = str(randint(0, 255))
        draw.text((x, 5), text=i, fill=f"rgb({R}, {G}, {B})", font=font)
        x += 20

    for i in range(1, randint(4, 6)):
        x1, y1 = randint(0, 110), randint(0, 40)
        x2, y2 = randint(0, 110), randint(0, 40)
        R = str(randint(0, 255))
        G = str(randint(0, 255))
        B = str(randint(0, 255))
        draw.line((x1, y1, x2, y2), fill=f"rgb({R}, {G}, {B})", width=2)

    image.save('code.png')
    # 返回画布(图片)和验证码图片里面的内容
    return [image, image_text]


create_image_content()

login.py:

from django.core.exceptions import ValidationError
from django.shortcuts import render, redirect, HttpResponse

from project_one.utils import pwd_data
from project_one.utils.code import create_image_content
from project_one.utils.PageData import PageData
from django import forms

from project_one import models
from io import BytesIO


# Create your views here.
class LoginForm(forms.Form):
    username = forms.CharField(label="用户名", widget=forms.TextInput(attrs={"placeholder": "用户名", "autocomplete": "off"}))
    password = forms.CharField(label="密码", widget=forms.PasswordInput(attrs={"placeholder": "密码", "autocomplete": "off", "type": "password"}))
    code = forms.CharField(label="验证码", widget=forms.TextInput(attrs={"placeholder": "验证码", "autocomplete": "off"}))

    # 对密码进行校验, 在校验函数里面, 我们对密码进行加密处理
    def clean_password(self):
        password = self.cleaned_data['password']
        return pwd_data.md5(password)


def login(request):
    if request.method == 'GET':
        form = LoginForm()
        return render(request, "login/login.html", {'form': form})
    form = LoginForm(request.POST)
    if form.is_valid():
        # 验证码校验
        user_input_code = form.cleaned_data.pop("code")
        code = str(request.session.get("image_code"))
        if user_input_code.upper() != code.upper():
            form.add_error("code", "验证码错误")
            return render(request, "login/login.html", {'form': form})
        print(form.cleaned_data)
        # 登录校验
        admin_object = models.AdminRole.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            form.add_error("password", "账号或密码错误")
            return render(request, "login/login.html", {'form': form})
        # 如果用户名和密码正确,即可登陆成功,将用户名和密码,身份信息存储在session当中
        request.session['info'] = {"id": admin_object.id, "username": admin_object.username, "password": admin_object.password, "role": admin_object.role}
        # 设置账号的时效期, 这里以秒为单位, 我们设置一个账号, 登录以后, 可以有一天时间使用, 时效期过去之后需要重新登录才可以继续使用网页
        request.session.set_expiry(60*60*24*1)
        return redirect('/')
    render(request, "login/login.html", {'form': form})


def logout(request):
    # 退出登录的时候, 清除session。
    request.session.clear()
    return redirect("/login/")


def image_code(request):
    image, text = create_image_content()
    request.session['image_code'] = text
    request.session.set_expiry(60)
    stream = BytesIO()
    image.save(stream, 'png')
    return HttpResponse(stream.getvalue())

urls.py:

"""project_simple URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from project_one.views import depart, user, assets, admin_role, login

urlpatterns = [
    # path('admin/', admin.site.urls),
    path("", depart.index, name="index"),
    path("depart/", depart.depart, name="depart"),
    path("depart/add/", depart.add_depart, name="add_depart"),
    path("depart/<int:nid>/modify/", depart.depart_modify, name="depart_modify"),
    path("depart/<int:nid>/del/", depart.del_depart, name="del_depart"),
    path("user/", user.user_info, name="user_info"),
    path("user/add/", user.user_add, name="user_add"),
    path("user/<int:nid>/modify/", user.user_modify, name="user_modify"),
    path("user/<int:nid>/del/", user.user_del, name="user_del"),
    path("user/add/modelform", user.user_add_modelform, name="user_add_modelform"),
    path("user/<int:nid>/modify/modelform", user.user_modify_modelform, name="user_modify_modelform"),
    path("assets_list/", assets.assets, name="assets"),
    path("assets/add/", assets.assets_add, name="assets_add"),
    path("assets/<int:nid>/modify/", assets.assets_modify, name="assets_modify"),
    path("assets/<int:nid>/del/", assets.assets_del, name="assets_del"),
    path("admin_list/", admin_role.admin, name="admin"),
    path("admin/add/", admin_role.admin_add, name="admin_add"),
    path("admin/<int:nid>/modify/", admin_role.admin_modify, name="admin_modify"),
    path("admin/<int:nid>/reset/pwd/", admin_role.admin_reset_pwd, name="admin_reset_pwd"),
    path("admin/<int:nid>/del/", admin_role.admin_del, name="admin_del"),
    path("login/", login.login, name="login"),
    path("logout/", login.logout, name="logout"),
    path("image/code/", login.image_code, name="image_code")
]

auth.py:

from django.shortcuts import render, redirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings


class AuthMiddleware(MiddlewareMixin):
    # 登录校验
    def process_request(self, request):
        # 避免打开login页面之后验证login页面, 不然就会产生死循环。
        # 这里需要忽略以下路由
        if request.path_info in ["/login/", "/logout/", "/image/code/"]:
            return
        info_dict = request.session.get('info')
        if info_dict:
            request.unicom_id = info_dict['id']
            request.unicom_username = info_dict['username']
            request.unicom_role = info_dict['role']
            return
        return redirect("/login/")

    def process_view(self, request, view_func, args, kwargs):
        if request.path_info in ["/login/", "/logout/", "/image/code/"]:
            return
        role = request.unicom_role
        user_permission_list = settings.UNICOM_PERMISSION[role]

        # 当前请求的路由name不在这个列表当中
        if request.resolver_match.url_name not in user_permission_list:
            return
        return HttpResponse("没有权限")

好了, 这篇文章的内容就到此结束了, 内容不多, 难度也不大, 但是不能忽略验证码这个功能, 因为验证码在网页里面很常见。

以上就是Django的账号登录及权限管理的所有内容了, 如果有哪里不懂的地方,可以把问题打在评论区, 欢迎大家在评论区交流!!!
如果我有写错的地方, 望大家指正, 也可以联系我, 让我们一起努力, 继续不断的进步.
学习是个漫长的过程, 需要我们不断的去学习并掌握消化知识点, 有不懂或概念模糊不理解的情况下,一定要赶紧的解决问题, 否则问题只会越来越多, 漏洞也就越老越大.
人生路漫漫, 白鹭常相伴!!!


网站公告

今日签到

点亮在社区的每一天
去签到