名人说:东边日出西边雨,道是无晴却有晴。——刘禹锡《竹枝词》
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)目录
- 1、登录界面
- 2、用户名密码校验
- 3、cookie与session配置
- ①cookie与session
- ②配置
- 4、登录验证
- 5、注销登录
- 6、图片验证码
- ①Pillow库
- ②图片验证码的实现
- 7、补充:图片验证码的作用和扩展
- ①作用
- ②其他类型的验证码
- 8、验证码校验
在上一篇博客中我们实现了管理员管理的内容,本篇博客则来实现用户登录,使系统的功能更加完善。
1、登录界面
1.在urls.py中添加用户列表的路径
login/
,并告诉该路径指向的视图account.login
from django.urls import path
from api.views import depart, user, pretty, admin, account
urlpatterns = [
# 部门管理
path("depart/list/", depart.depart_list),
path("depart/add/", depart.depart_add),
path("depart/delete/", depart.depart_delete),
path("depart/<int:nid>/edit/", depart.depart_edit),
# 用户管理
path("user/list/", user.user_list),
path("user/add/", user.user_add),
path("user/model/form/add/", user.user_model_form_add),
path('user/<int:nid>/edit/', user.user_edit),
path("user/<int:nid>/delete/", user.user_delete),
# 靓号管理
path("pretty/list/", pretty.pretty_list),
path("pretty/add/", pretty.pretty_add),
path("pretty/<int:nid>/edit/", pretty.pretty_edit),
path("pretty/<int:nid>/delete/", pretty.pretty_delete),
# 管理员管理
path('admin/list/', admin.admin_list),
path('admin/add/', admin.admin_add),
path('admin/<int:nid>/edit/', admin.admin_edit),
path('admin/<int:nid>/delete/', admin.admin_delete),
path('admin/<int:nid>/reset/', admin.admin_reset),
# 用户登录
path('login/', account.login),
]
2.在views文件夹下新建account.py,并在account.py中写出对应的函数,发出请求,并返回响应
login.html
"""
from django.shortcuts import render
这段代码是导入Django中的一个函数`render`,用于渲染HTML模板并返回一个HttpResponse对象。
render函数通常在视图函数中使用,将数据和模板作为参数传递给它,然后它会根据***模板和数据生成HTML页面***,并返回给浏览器。
"""
from django.shortcuts import render, HttpResponse, redirect
from django import forms
from api.utils.bootstrap import BootStrapForm
from api.utils.encrypt import md5
from api.models import Admin
from api.utils.code import check_code
from io import BytesIO
# 这一次采用Form来实现
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=True,
)
password = forms.CharField(
label="用户名",
# render_value=True 表示当提交后,如果密码输入错误,不会自动清空密码输入框的内容
widget=forms.PasswordInput(attrs={"class": "form-control"}, ),
required=True,
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput,
required=True
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
def login(request):
"""登录"""
if request.method == "GET":
form = LoginForm()
return render(request, 'login.html', {"form": form})
form = LoginForm(data=request.POST)
if form.is_valid():
# 验证成功, 获取到的用户名和密码
print(form.cleaned_data)
# 验证码的校验
user_input_code = form.cleaned_data.pop('code')
image_code = request.session.get('image_code', "")
if image_code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {"form": form})
# 去数据库校验用户名和密码是否正确
admin_object = Admin.objects.filter(**form.cleaned_data).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {"form": form})
# 用户名密码和验证码正确
# 网站生成随机字符串,写到用户浏览器的cookie中,再写入到服务器的session中
request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/admin/list/")
return render(request, 'login.html', {"form": form})
3.创建templates目录下模版html文login.html,以此呈现做用户登录的界面。
模板layout.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--Bootstrap框架-->
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<!--datetimepicker插件-->
<link rel="stylesheet" type="text/css"
href="{% static 'plugins/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css' %}">
{% block css %}
<style>
.navbar {
border-radius: 0;
}
</style>
{% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">用户管理系统</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/admin/list">管理员账户</a></li>
<li><a href="/depart/list">部门管理</a></li>
<li><a href="/user/list">用户管理</a></li>
<li><a href="/pretty/list">靓号管理</a></li>
{# <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>#}
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/login">登录</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.session.info.name }}<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<li><a href="#">我的信息</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">注销</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div>
<div class="container">
{% block content %}{% endblock %}
</div>
</div>
{% block js %}
<script src="{% static 'js/jquery.min.js' %}"></script>
<!-- 加载 Bootstrap DateTimePicker JS -->
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js' %}"></script>
<script type="text/javascript">
$(function () {
//当容器加载完成,对容器调用工具函数
$("#dt").datetimepicker({
language: 'zh-CN', //语言
format: 'yyyy-mm-dd',//日期的格式
minView: 'month', //可以选择的最小视图
initialDate: new Date(),//初始化显示的日期
autoclose: true,//设置选择完日期或者时间之后,日否自动关闭日历
todayBtn: true,//设置自动显示为今天
clearBtn: false//设置是否清空按钮,默认为false
});
});
</script>
{% endblock %}
login.html
{% extends 'layout.html' %}
{% block css %}
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<h2>用户登录</h2>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.errors.username.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.errors.password.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
<input type="text" name="code" class="form-control" placeholder="请输入图片验证码" required="" id="id_code">
<span style="color: red;"></span>
</div>
<div class="col-xs-5">
<img src="/image/code/" alt="" id="image_code" onclick="this.setAttribute('src','/image/code/?random='+Math.random())">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary center-block" style="width: 80px;">登 录</button>
</form>
</div>
</div>
{% endblock %}
效果:
2、用户名密码校验
在用户登录时,我们需要考虑一个问题,用户所输入的用户名和密码是否正确?是否与数据库中存储的用户名和密码一致?接下来我们就来解决这些问题。
1.修改login.html、form.py和account.py
a.修改login.html
{% extends 'layout.html' %}
{% block css %}
<style>
.account {
width: 400px;
border: 1px solid #dddddd;
border-radius: 5px;
box-shadow: 5px 5px 20px #aaa;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 20px 40px;
}
.account h2 {
margin-top: 10px;
text-align: center;
}
</style>
{% endblock %}
{% block content %}
<div class="account">
<h2>用户登录</h2>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>用户名</label>
{{ form.username }}
<span style="color: red;">{{ form.errors.username.0 }}</span>
</div>
<div class="form-group">
<label>密码</label>
{{ form.password }}
<span style="color: red;">{{ form.errors.password.0 }}</span>
</div>
<div class="form-group">
<label for="id_code">图片验证码</label>
<div class="row">
<div class="col-xs-7">
<input type="text" name="code" class="form-control" placeholder="请输入图片验证码" required="" id="id_code">
<span style="color: red;"></span>
</div>
<div class="col-xs-5">
<img src="/image/code/" alt="" id="image_code" onclick="this.setAttribute('src','/image/code/?random='+Math.random())">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary center-block" style="width: 80px;">登 录</button>
</form>
</div>
</div>
{% endblock %}
在templates目录下新建error.html
error.html
{% extends 'layout.html' %}
{% block content %}
<div class="container">
<div class="alert alert-danger" role="alert">{{ msg }}</div>
</div>
{% endblock %}
b.在form.py新增用户登录模块,此次采用Form来实现。
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=True
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=True),
required=True
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=True,
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
c.修改account.py
"""
from django.shortcuts import render
这段代码是导入Django中的一个函数`render`,用于渲染HTML模板并返回一个HttpResponse对象。
render函数通常在视图函数中使用,将数据和模板作为参数传递给它,然后它会根据***模板和数据生成HTML页面***,并返回给浏览器。
"""
from django.shortcuts import render, HttpResponse, redirect
from django import forms
from api.utils.bootstrap import BootStrapForm
from api.utils.encrypt import md5
from api.models import Admin
from api.utils.code import check_code
from io import BytesIO
# 这一次采用Form来实现
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control"}),
required=True,
)
password = forms.CharField(
label="用户名",
# render_value=True 表示当提交后,如果密码输入错误,不会自动清空密码输入框的内容
widget=forms.PasswordInput(attrs={"class": "form-control"}, ),
required=True,
)
code = forms.CharField(
label="验证码",
widget=forms.TextInput,
required=True
)
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)
def login(request):
"""登录"""
if request.method == "GET":
form = LoginForm()
return render(request, 'login.html', {"form": form})
form = LoginForm(data=request.POST)
if form.is_valid():
# 验证成功, 获取到的用户名和密码
print(form.cleaned_data)
# 验证码的校验
user_input_code = form.cleaned_data.pop('code')
image_code = request.session.get('image_code', "")
if image_code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {"form": form})
# 去数据库校验用户名和密码是否正确
admin_object = Admin.objects.filter(**form.cleaned_data).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {"form": form})
# 用户名密码和验证码正确
# 网站生成随机字符串,写到用户浏览器的cookie中,再写入到服务器的session中
request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/admin/list/")
return render(request, 'login.html', {"form": form})
效果:输入用户名或密码错误时,此时登录时会提示“用户名或密码错误”
3、cookie与session配置
在解决了用户名密码校验后,又遇到了一个问题,那就是我们平时访问一些网站会提示是否接受cookies?它是什么?有什么用?我们项目中怎么引入来它呢?
①cookie与session
Cookie和Session都是用于维持用户状态和数据的机制,它们在Web应用中扮演着重要的角色。下面我将用中文详细解释Cookie和Session的概念,并使用Markdown进行格式化。
1.Cookie
Cookie是一种在客户端(通常是浏览器)存储数据的机制。它以键值对的形式存储在浏览器中,由服务器在HTTP响应头中设置,并在后续的请求中发送回服务器。
-
特点:
- 存储在客户端浏览器中
- 大小限制:一般不超过4KB
- 可以设置过期时间
- 可以设置访问路径和域名
- 每次请求都会自动携带相应的Cookie
-
用途:
- 会话管理:记录用户的登录状态、购物车等
- 个性化设置:记录用户的偏好、主题等
- 行为跟踪:记录用户的浏览行为、广告点击等
2.Session
Session是在服务器端存储用户状态和数据的机制。它基于Cookie实现,但数据存储在服务器端。当用户第一次访问网站时,服务器会生成一个唯一的Session ID,并将其以Cookie的形式发送给客户端。后续的请求中,客户端会将Session ID发送回服务器,服务器根据Session ID获取对应的用户数据。
-
特点:
- 存储在服务器端
- 大小限制:理论上没有限制,取决于服务器的内存大小
- 安全性相对较高,数据不容易被篡改
- 依赖Cookie传递Session ID
-
用途:
- 用户认证:存储用户的登录状态
- 敏感数据的存储:存储用户的个人信息、权限等
- 服务器端数据共享:在不同的页面或请求之间共享数据
3.Cookie与Session的区别
特性 | Cookie | Session |
---|---|---|
存储位置 | 客户端(浏览器) | 服务器端 |
大小限制 | 一般不超过4KB | 理论上没有限制,取决于服务器的内存大小 |
安全性 | 相对较低,数据容易被篡改 | 相对较高,数据存储在服务器端 |
数据传递 | 每次请求都会自动携带相应的Cookie | 依赖Cookie传递Session ID |
用途 | 会话管理、个性化设置、行为跟踪等 | 用户认证、敏感数据存储、服务器端数据共享 |
②配置
1.修改account.py中的login函数
def login(request):
"""登录"""
if request.method == "GET":
form = LoginForm()
return render(request, 'login.html', {"form": form})
form = LoginForm(data=request.POST)
if form.is_valid():
# 验证成功, 获取到的用户名和密码
print(form.cleaned_data)
# 验证码的校验
user_input_code = form.cleaned_data.pop('code')
image_code = request.session.get('image_code', "")
if image_code.upper() != user_input_code.upper():
form.add_error("code", "验证码错误")
return render(request, 'login.html', {"form": form})
# 去数据库校验用户名和密码是否正确
admin_object = Admin.objects.filter(**form.cleaned_data).first()
if not admin_object:
form.add_error("password", "用户名或密码错误")
return render(request, 'login.html', {"form": form})
# 用户名密码和验证码正确
# 网站生成随机字符串,写到用户浏览器的cookie中,再写入到服务器的session中
request.session["info"] = {'id': admin_object.id, 'name': admin_object.username}
request.session.set_expiry(60 * 60 * 24 * 7)
return redirect("/admin/list/")
return render(request, 'login.html', {"form": form})
在登录成功后,页面会跳转到管理员界面,而session信息将保存在服务端数据库django_session表中。
2.mysql显示表中数据
mysql> show tables;
+----------------------------+
| Tables_in_henan16 |
+----------------------------+
| api_admin |
| api_department |
| api_prettynum |
| api_userinfo |
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
14 rows in set (0.00 sec)
mysql> select * from django_session;
+----------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+
| session_key | session_data | expire_date |
+----------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+
| z4ujk56tzhhareeh77z353vvm0upmaca | eyJpbWFnZV9jb2RlIjoiRklLUkYiLCJfc2Vzc2lvbl9leHBpcnkiOjYwfQ:1rlAeT:qgyi412exxDZPTwag0HYh7jtGUyc-Kav-VvQ_2MxsaI | 2024-03-15 16:44:25.482192 |
+----------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------+
1 rows in set (0.01 sec)
4、登录验证
在实现了这个用户名密码验证之后,我们要对每个页面进行验证,只有登录的人才能访问这些页面,怎么实现登录验证?这个问题我们学了中间件后就会变得越发清晰。
中间件会在视图函数下的每个方法执行前调用,不用再每个方法下进行判断,不然会导致函数过多显得冗杂。
1.新建middleware目录,并在该目录下新建
auth.py
文件
auth.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse, redirect
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
# 0.排除那些不需要登录就能访问的页面
# request.path_info 获取当前用户请求的URL /login/
if request.path_info in ["/login/", "/image/code/"]:
return
# 1.读取当前访问的用户的session信息,如果能读到,说明已登陆过,就可以继续向后走。
info_dict = request.session.get("info")
# print(info_dict)
if info_dict:
return
# 2.没有登录过,重新回到登录页面
return redirect('/login/')
2.修改settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
'api.middleware.auth.AuthMiddleware',
]
到了这里,你会发现,只要你没登陆过,你无论访问哪个板块的页面,都会跳转到登录界面。
那么此时登录校验的功能就实现了,但是由于目前无法退出,说明该用户登录功能仍有缺陷需要改善,接下来咱们就对这一部分做进一步的介绍。
5、注销登录
1.在layout.html中配置注销(退出登录的)的跳转URL路径。
layout.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--Bootstrap框架-->
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/font-awesome-4.7.0/css/font-awesome.css' %}">
<!--datetimepicker插件-->
<link rel="stylesheet" type="text/css"
href="{% static 'plugins/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css' %}">
{% block css %}
<style>
.navbar {
border-radius: 0;
}
</style>
{% endblock %}
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">用户管理系统</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/admin/list">管理员账户</a></li>
<li><a href="/depart/list">部门管理</a></li>
<li><a href="/user/list">用户管理</a></li>
<li><a href="/pretty/list">靓号管理</a></li>
{# <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>#}
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/login">登录</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.session.info.name }}<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<li><a href="#">我的信息</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">注销</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div>
<div class="container">
{% block content %}{% endblock %}
</div>
</div>
{% block js %}
<script src="{% static 'js/jquery.min.js' %}"></script>
<!-- 加载 Bootstrap DateTimePicker JS -->
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js' %}"></script>
<script type="text/javascript">
$(function () {
//当容器加载完成,对容器调用工具函数
$("#dt").datetimepicker({
language: 'zh-CN', //语言
format: 'yyyy-mm-dd',//日期的格式
minView: 'month', //可以选择的最小视图
initialDate: new Date(),//初始化显示的日期
autoclose: true,//设置选择完日期或者时间之后,日否自动关闭日历
todayBtn: true,//设置自动显示为今天
clearBtn: false//设置是否清空按钮,默认为false
});
});
</script>
{% endblock %}
2.在urls.py中添加用户列表的路径
logout/
,并告诉该路径指向的视图view.logout
urls.py
from django.urls import path
from api.views import depart, user, pretty, admin, account
urlpatterns = [
# 部门管理
path("depart/list/", depart.depart_list),
path("depart/add/", depart.depart_add),
path("depart/delete/", depart.depart_delete),
path("depart/<int:nid>/edit/", depart.depart_edit),
# 用户管理
path("user/list/", user.user_list),
path("user/add/", user.user_add),
path("user/model/form/add/", user.user_model_form_add),
path('user/<int:nid>/edit/', user.user_edit),
path("user/<int:nid>/delete/", user.user_delete),
# 靓号管理
path("pretty/list/", pretty.pretty_list),
path("pretty/add/", pretty.pretty_add),
path("pretty/<int:nid>/edit/", pretty.pretty_edit),
path("pretty/<int:nid>/delete/", pretty.pretty_delete),
# 管理员管理
path('admin/list/', admin.admin_list),
path('admin/add/', admin.admin_add),
path('admin/<int:nid>/edit/', admin.admin_edit),
path('admin/<int:nid>/delete/', admin.admin_delete),
path('admin/<int:nid>/reset/', admin.admin_reset),
# 用户登录
path('login/', account.login),
path('logout/', account.logout),
]
3.在account.py中新增logout函数,用于实现用户注销登录功能。
account.py
def logout(request):
"""用户注销"""
# 清除session
request.session.clear()
return redirect("/login/")
效果:
6、图片验证码
为了使用户登录功能更加完善,加入图片验证码的功能,上面的内容大家已经可以看到图片验证码,并且代码已经在上面体现了,那接下来重要讲解一下图片验证码的实现思路。
在此之前我们先要认识一个库pillow:
①Pillow库
Pillow是一个Python图像处理库,它是Python Imaging Library (PIL) 的一个分支。Pillow提供了丰富的图像处理功能,可以进行图像的打开、保存、裁剪、调整大小、旋转、滤镜应用、颜色转换等操作。它支持多种常见的图像格式,包括JPEG、PNG、GIF、BMP等。
Pillow库易于安装和使用,并且在处理图像时提供了简洁而直观的API。它广泛应用于Web开发、数据分析、图像处理等领域,可以用来处理图像、生成缩略图、添加水印、图像识别等任务。
1.下载pillow
pip install pillow
2.验证码字体下载
验证码字体下载链接,点击此处下载
3.下载完之后,解压,并在utils目录下新建ttf目录,将字体文件复制到该目录下。
效果:
②图片验证码的实现
1.在urls.py中添加用户列表的路径
image/code/
,并告诉该路径指向的视图account.image_code
from django.urls import path
from api.views import depart, user, pretty, admin, account
urlpatterns = [
# 部门管理
path("depart/list/", depart.depart_list),
path("depart/add/", depart.depart_add),
path("depart/delete/", depart.depart_delete),
path("depart/<int:nid>/edit/", depart.depart_edit),
# 用户管理
path("user/list/", user.user_list),
path("user/add/", user.user_add),
path("user/model/form/add/", user.user_model_form_add),
path('user/<int:nid>/edit/', user.user_edit),
path("user/<int:nid>/delete/", user.user_delete),
# 靓号管理
path("pretty/list/", pretty.pretty_list),
path("pretty/add/", pretty.pretty_add),
path("pretty/<int:nid>/edit/", pretty.pretty_edit),
path("pretty/<int:nid>/delete/", pretty.pretty_delete),
# 管理员管理
path('admin/list/', admin.admin_list),
path('admin/add/', admin.admin_add),
path('admin/<int:nid>/edit/', admin.admin_edit),
path('admin/<int:nid>/delete/', admin.admin_delete),
path('admin/<int:nid>/reset/', admin.admin_reset),
# 用户登录
path('login/', account.login),
path('logout/', account.logout),
path('image/code/', account.image_code),
]
2.在utils目录下新建code.py文件,该文件用于生成验证码。
code.py
from PIL import Image, ImageDraw, ImageFilter, ImageFont
import random
def check_code(width=120, height=30, char_length=5, font_file='api/utils/ttf/Monaco.ttf', font_size=28):
code = []
img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGB')
def rndChar():
"""
生成随机字母
:return:
"""
return chr(random.randint(65, 90))
def rndColor():
"""
生成随机颜色
:return:
"""
return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
# 写文字
font = ImageFont.truetype(font_file, font_size)
for i in range(char_length):
char = rndChar()
code.append(char)
h = random.randint(0, 4)
draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
# 写干扰点
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
# 写干扰圆圈
for i in range(40):
draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
x = random.randint(0, width)
y = random.randint(0, height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
# 画干扰线
for i in range(5):
x1 = random.randint(0, width)
y1 = random.randint(0, height)
x2 = random.randint(0, width)
y2 = random.randint(0, height)
draw.line((x1, y1, x2, y2), fill=rndColor())
img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
return img, ''.join(code)
if __name__ == '__main__':
img, code_str = check_code()
print(code_str)
with open('code.png', 'wb') as f:
img.save(f, format='png')
3.修改account.py,在其中添加image_code函数,并导入code.py,以实现图片验证码的生成。
account.py
def image_code(request):
"""图片验证码"""
# 调用pillow函数,生成图片
img, code_string = check_code()
# 写入自己的session
request.session['image_code'] = code_string
# 设置60s超时
request.session.set_expiry(60)
# 将图片保存到内存里
stream = BytesIO()
img.save(stream, 'png')
return HttpResponse(stream.getvalue())
效果:
7、补充:图片验证码的作用和扩展
①作用
在用户登录时增加图片验证码的主要目的是提高网站的安全性,防止一些自动化程序或者恶意攻击。具体如下:
-
防止暴力破解
-
防止大量无效请求
-
防止自动注册和垃圾评论
-
提高用户体验
总的来说,图片验证码是一种简单而有效的安全措施,可以防止一些常见的攻击方式,提高网站的安全性和用户体验。虽然它并不是万能的,但它可以与其他安全措施(如密码复杂度要求、多因素身份验证等)结合使用,构建一个更加安全的登录系统。
②其他类型的验证码
-
文本验证码:通过纯文本的形式出现,通常是一些简单的问题或者算术题,用户需要填写正确的答案。
-
滑动验证码:用户需要按照提示拖动滑块到指定位置,通过人机交互的方式完成验证。
-
短信验证码:网站向用户的手机发送一个随机的验证码,用户需要将收到的验证码填写到网站上进行验证。
-
行为验证码:通过分析用户的行为模式(如鼠标轨迹、键盘输入速度等)来判断。
在实现了图片验证码后,我们还要实现验证码的校验,即判断用户输入的验证码与生成的验证码是否一样,增加了验证码校验之后,网站的安全性也会得到一定的提升。
8、验证码校验
到了这里我们的图片验证码和验证码校验都完成了,但是还可以做一个小的优化,就是点击图片刷新验证码,我们只需要在login.html文件中修改img行就可以。
<img src="/image/code/" alt="" id="image_code" onclick="this.setAttribute('src','/image/code/?random='+Math.random())">
怎么理解这行代码?
这行代码其实是一个HTML的<img>
标签,它用于显示图像:
src="/image/code/"
:指定了图像的来源路径为"/image/code/",这里可能是一个服务器上的图像文件或者一个处理图像验证码的URL。alt=""
:指定了图像的替代文本,当图像无法加载时会显示该文本。id="image_code"
:为图像元素指定了一个唯一的标识符,可以在JavaScript中使用该标识符来操作该元素。onclick="this.setAttribute('src','/image/code/?random='+Math.random())"
:定义了一个点击事件处理函数,当图像被点击时,会执行这个函数。函数的作用是将图像的src
属性设置为"/image/code/?random="加上一个随机数,这样可以强制浏览器重新加载图像,实现刷新验证码的效果。
总的来说,这行代码是在HTML中创建了一个图像元素,并定义了点击事件处理函数,用于刷新图像验证码。当图像被点击时,会通过设置src
属性为一个新的URL来重新加载图像。
最后整体效果:
在完成了部门管理、用户管理、靓号管理、管理员管理、用户登录等之后,我们这一部分的内容就暂时告一段落,接下来会学习ajax的内容,我们为什么要学习ajax,因为我们实现网页内容的动态更新和无刷新的用户交互可以用到它,它通过在后台与服务器进行数据交换,使网页能够在不刷新整个页面的情况下更新部分内容,这样系统的交互性会更好。
很感谢你能看到这里,如有相关疑问,还请下方评论留言。
Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
希望本篇内容能对大家有所帮助,如果大家喜欢的话,请动动手点个赞和关注吧,非常感谢你们的支持!