10.3  CSRF防护

  CSRF(跨站请求伪造)也成为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用,窃取网站的用户信息来制作恶意请求。

  Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrftoken的隐含值,这个隐含值会与网站后台保存的csrftoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:

    1、在用户访问网站时,Django在网页的表单中生成一个隐含字段csrftoken,这个值是在服务器端随机生成的。

    2、当用户提交表单时,服务器检验表单的csrftoken是否和自己保存的csrftoken一致,用来判断当前请求是否合法。

    3、如果用户被CSRF攻击并从其他地方发生攻击请求,由于其他地方不可能知道隐藏的csrftoken信息,因此导致网站后台校验csrftoken失败,攻击就被成功防御。

  在Django中使用CSRF防护功能,首先在配置文件settings.py中设置防护功能的配置信息。功能的开启由配置文件的中间件django.middleware.csrf.CsrfViewMiddleware实现,在创建项目时已默认开启,如下图:

设置CSRF防护

  CSRF防护只作用于POST请求,并不防护GET请求,因为GET请求以只读形式访问网站资源,并不破坏和篡改网站数据。以MyDjango为例,在模板user.html的表单<form>标签中加入内置标签csrf_token即可实现CSRF防护,代码如下:

#user/user.html部分代码
<div class="flex-center">
<div class="container">
<div class="flex-center">
<div class="unit-1-2 unit-1-on-mobile">
<h1>MyDjango Auth</h1>
{% if tips %}
<div>{{ tips }}</div>
{% endif %}
<form class="form" action="" method="post">
{% csrf_token %}
<div>用户名:<input type="text" name='username'></div>
<div>密 码:<input type="password" name='password'></div>
<button type="submit" class="btn btn-primary btn-block">确定</button>
</form>
</div>
</div>
</div>
</div>

  启动运行MyDjango,在浏览器中打开用户登录页面,然后查看页面的源码,可以发现表单新增隐藏域,隐藏域是由模板语法{%  csrf_token  %}所生成的,网站生成的csrftoken都会记录在隐藏域的value属性中。当用户每次提交表单时,csrftoken都会随之变化,如下图:

csrftoken信息

  如果想要取消表单的CSRF防护,可以在模板上删除{%  csrf_token  %},并且在相应的视图函数中添加装饰器@csrf_exempt,代码如下:

from django.views.decorators.csrf import csrf_exempt

#取消CSRF防护
@csrf_exempt
def registerView(request):
pass
return render(request, 'user.html', locals())

  如果只是在模板上删除{%  csrf_token  %},并没有在相应的视图函数中设置过滤器@csrf_exempt,那么当用户提交表单时,程序因CSRF验证失败而抛出403异常的页面,如下图:

CSRF验证失败

  最后还有一种比较特殊的情况,如果在配置文件settings.py中删除中间件CsrfViewMiddleware,这样使整个网站都取消CSRF防护。在全站没有CSRF防护的情况下,又想对某些请求设置CSRF防护,那么在模板上添加模板语法{%  csrf_token  %},然后在相应的视图函数中添加装饰器@csrf_protect即可实现,代码如下:

from django.views.decorators.csrf import csrf_protect, csrf_exempt

#添加CSRF防护
@csrf_protect
def registerView(request):
pass
return render(request, 'user.html', locals())

  值得注意的是,在日常开发中,如果网页某些数据时使用前端的Ajax实现表单提交的,那么Ajax向服务器发送POST请求时,请求参数必须添加csrftoken的信息,否则服务器会视该请求是恶意请求。实现代码如下:

<script>
function submitForm() {
var csrf = $('input[name="csrfmiddlewaretoken"]').val();
var user = $('#user').val();
var password = $('#password').val();
$.ajax({
url: '/csrf1.html',
type: 'POST',
data: {'user': user,
'password': password,
'csrfmiddlewaretoken': csrf,}
success: function (arg) {
console.log(arg);
}
})
}
</script>

10.4  消息提示

  在网页应用中,当处理完表单或完成其他信息输入后,网站会有相应的操作提示。Django有内置消息提示功能供开发者直接使用,信息提示功能由中间件SessionMiddleware、MessageMiddleware和INSTALLED_APPS的django.contrib.messages共同实现。在创建Django项目时,消息提示功能已默认开启,如下图:

消息提示功能配置

  消息提示必须依赖中间件SessionMiddleware,因为消息提示的引擎默认是SessionStorage,而SessionStorage是在Session的基础上实现的,同时说明了中间件SessionMiddleware为什么设置在MessageMiddleware的前面。

  使用信息提示功能之前,需要了解消息提示的类型,Django提供了5种消息类型,说明如下表所示:

类型 说明
DEBUG 提示开发过程中的相关调式信息
INFO 提示信息,如用户信息
SUCCESS 提示当前操作执行成功
WARNING 警告当前操作存在风险
ERROR 提示当前操作错误

Django提供的5种消息类型

  若想在开发中使用消息提示,首先在视图函数中生成相关的信息内容,然后在模板中将信息内容展现在网页上。因此,在index中定义相关的URL地址和相应的视图函数,代码如下:

from django.urls import path
from . import views
urlpatterns = [
# 首页的URL
path('', views.index, name='index'),
# 购物车
path('ShoppingCar.html', views.ShoppingCarView, name='ShoppingCar'),
# 消息提示
path('message.html', views.messageView, name='message'),
] #index/index.html
from django.template import RequestContext
from django.contrib import messages # 消息提示
def messageView(request):
# 信息添加方法一
messages.info(request, '信息提示')
messages.success(request, '信息正确')
messages.warning(request, '信息警告')
messages.error(request, '信息错误')
# 信息添加方法二
messages.add_message(request, messages.INFO, '信息提示')
return render(request, 'message.html', locals(), RequestContext(request))

  在视图函数messageView中可以看到添加信息有两种方式,两者实现的功能是一样的。在函数返回时,必须设置RequestContext(request),这是Django的上下文处理器,确保信息messages对象能在模板中使用。最后在index的template中创建模板message.html,模板代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>信息提示</title>
</head>
<body>
{% if messages %}
<ul>
{% for message in messages %}
{# message.tags代表信息类型 #}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% else %}
<script>alert('暂无信息');</script>
{% endif %}
</body>
</html>

  在上述例子中,视图函数messageView将对象messages通过上下文处理器RequestContext(request)传递给模板变量messages,然后将模板变量messages的内容遍历输出,最后通过模板引擎解析生成HTML网页。在浏览器上访问http://127.0.0.1:8000/message.html,网页信息如下图:

消息提示功能应用

10.5  分页功能

  在网页上浏览数据的时候,数据列表的下方都能看到翻页功能,而且每一页的数据都不相同。比如在淘宝上搜索某商品的关键字,淘宝会根据用户提供的关键字返回符合条件的商品信息,并且对这些商品信息进行分页处理,用户可以在商品信息的下方单击相应的页数按钮查看。

  如果要实现数据的分页功能,需要考虑多方面因素:

    1、当前用户访问的页数是否存在上(下)一页。

    2、访问的页数是否超出页数上限。

    3、数据如何按页截取,如何设置每页的数据量

  Django已为开发者提供了内置的分页功能,开发者无须自己实现数据分页功能,只需调用Django内置分页功能的函数即可实现。在实现网站数据分页之前,首先了解Django的分页功能为开发者提供了那些方法与函数,在PyCharm的Terminal中开启Django的shell模式,函数使用说明如下:

(py3_3) E:\test5\MyDjango>python manage.py shell

#导入分页功能模块
In [1]: from django.core.paginator import Paginator
#生成数据列表
In [2]: objects = [chr(x) for x in range(97,107)] In [3]: objects
Out[3]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
#将数据列表以每三个元素分为一页
In [4]: p = Paginator(objects, 3)
#输出全部数据,即整个数据列表
In [5]: p.object_list
Out[5]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
#获取数据列表的长度
In [6]: p.count
Out[6]: 10 #分页后的总页数
In [7]: p.num_pages
Out[7]: 4
#将页数转换成range循环对象
In [8]: p.page_range
Out[8]: range(1, 5)
#获取第二页的数据信息
In [9]: page2 = p.page(2)
#判断第二页是否存在上一页
In [10]: page2.has_previous()
Out[10]: True
#如果当前页数存在上一页,就输出上一页的页数,否则抛出EmptyPage异常
In [11]: page2.previous_page_number()
Out[11]: 1
#判断第二页是否存在下一页
In [12]: page2.has_next()
Out[12]: True #如果当前页数存在下一页,就输出下一页的页数,否则抛出EmptyPage异常
In [13]: page2.next_page_number()
Out[13]: 3
#输出第二页所对应的数据内容
In [14]: page2.object_list
Out[14]: ['d', 'e', 'f']
#输出第二页的第一条数据在整个数据列表的位置,数据位置从1开始计算
In [15]: page2.start_index()
Out[15]: 4
#输出第二页的最后一条数据在整个数据列表的位置,数据位置从1开始计算
In [16]: page2.end_index()
Out[16]: 6

  上述代码是Django分页功能的使用方法,根据对象类型可以将代码分为两部分:分页对象p和某分页对象page2,两者说明如下:

    (1)分页对象p:由模块Paginator实例化生成。在Paginator实例化时,需要传入参数object和per_page,参数object是待分页的数据对象,参数per_page用于设置每页的数据量。对象p提供表所示的函数:

函数 说明
object_list 输出被分页的全部数据,即数据列表objects
Count 获取当前被分页的数据总量,即数据列表objects的长度
num_pages 获取分页后的总页数
page_range 将总页数转换成range循环对象
page(number) 获取某一页的数据对象,参数number代表页数

    (2)某分页对象page2:由对象p使用函数page所生成的对象。page2提供表所示的函数:

函数 说明
has_previous() 判断当前页数是否存在上一页
previous_page_number() 如果当前页数存在上一页,输出上一页的页数,否则抛出EmptyPage异常
has_next() 判断当前页数是否存在下一页
next_page_number() 如果当前页数存在下一页,输出下一页的页数,否则抛出EmptyPage异常
object_list 输出当前分页的数据信息
start_index() 输出当前分页的第一条数据在整个数据列表的位置,数据位置以1开始计算
end_index() 输出当前分页的最后一条数据在整个数据列表的位置,数据位置以1开始计算

  我们通过一个示例来讲述如何在开发过程中使用Django内置分页功能。在MyDjango的数据库中分别对数据表index_type和index_product添加数据信息,数据来源于第6章,可以在本书源码中找到数据文件,如下图所示:

  然后在index中添加模板pagination.html,模板的样式文件common.css和pagination.css存放在index的静态文件夹static中,如下图:

  完成项目的环境搭建后,本示例的分页功能需要由index的urls.py、views.py和pagination.html共同实现,首先在urls.py和views.py中分别添加以下代码:

#index/urls.py
from django.urls import path
from . import views urlpatterns = [
# 首页
path('', views.index, name='index'),
# 购物车
path('ShoppingCar.html', views.ShoppingCarView, name='ShoppingCar'),
# 分页功能
path('pagination/<int:page>.html', views.paginationView, name='pagination'),
] #index/views.py
#views.py的paginationView函数
#导入paginator模块
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
#分页功能
def paginationView(request, page):
# 获取数据表index_product全部数据
Product_list = Product.objects.all()
# 设置每一页的数据量为3
paginator = Paginator(Product_list, 3)
try:
pageInfo = paginator.page(page)
except PageNotAnInteger:
# 如果参数page的数据类型不是整型,则返回第一页数据
pageInfo = paginator.page(1)
except EmptyPage:
# 用户访问的页数大于实际页数,则返回最后一页的数据
pageInfo = paginator.page(paginator.num_pages)
return render(request, 'pagination.html', locals())

  上述代码设置了分页功能的URL地址和相应的视图函数,其说明如下:

    1、URL地址设置了动态变量page,该变量代表用户当前访问的页数。

    2、函数paginationView首先获取数据表index_product中的全部数据,生成变量Product_list。

    3、通过分页模块paginator对变量Product_list进行分页,以每3条数据划分为一页。

    4、使用函数page获取分页对象paginator中某一页分页的数据信息。

    5、当变量page传入函数page时,如果变量page不是整型,程序就会抛出PageNotAnInteger异常,然后返回第一页的数据。

    5、如果变量page的数值大于总页数,程序就会抛出EmptyPage异常,然后返回最后一页的数据。异常PageNotAnInteger和EmptyPage都来自于模块paginator。

  将视图函数处理的结果传给模板文件pagination.html,然后把分页后的数据展示在网页中。模板文件pagination.html的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>分页功能</title>
{# 导入CSS样式文件 #}
{% load staticfiles %}
<link type="text/css" rel="stylesheet" href="{% static "css/common.css" %}">
<link type="text/css" rel="stylesheet" href="{% static "css/pagination.css" %}">
</head>
<body>
<div class="wrapper clearfix" id="wrapper">
<div class="mod_songlist">
<ul class="songlist__header">
<li class="songlist__header_name">产品名称</li>
<li class="songlist__header_author">重量</li>
<li class="songlist__header_album">尺寸</li>
<li class="songlist__header_other">产品类型</li>
</ul>
<ul class="songlist__list">
{# 列出当前分页所对应的数据内容 #}
{% for item in pageInfo %}
<li class="js_songlist__child" mid="1425301" ix="6">
<div class="songlist__item">
<div class="songlist__songname">{{item.name}}</div>
<div class="songlist__artist">{{item.weight}}</div>
<div class="songlist__album">{{item.size}}</div>
<div class="songlist__other">{{ item.type }}</div>
</div>
</li>
{% endfor %}
</ul>
{# 分页导航 #}
<div class="page-box">
<div class="pagebar" id="pageBar">
{# 上一页的URL地址 #}
{% if pageInfo.has_previous %}
<a href="{% url 'pagination' pageInfo.previous_page_number %}" class="prev"><i></i>上一页</a>
{% endif %}
{# 列出所有的URL地址 #}
{% for num in pageInfo.paginator.page_range %}
{% if num == pageInfo.number %}
<span class="sel">{{ pageInfo.number }}</span>
{% else %}
<a href="{% url 'pagination' num %}" target="_self">{{num}}</a>
{% endif %}
{% endfor %}
{# 下一页的URL地址 #}
{% if pageInfo.has_next %}
<a href="{% url 'pagination' pageInfo.next_page_number %}" class="next">下一页<i></i></a>
{% endif %}
</div>
</div>
</div><!--end mod_songlist-->
</div><!--end wrapper-->
</body>
</html>

pagination.html

  完成urls.py、views.py和pagination.html的代码编写后,最后测试功能是否正常运行。启动项目并在浏览器上访问http://127.0.0.1:8000/pagination/1.html,单击分页导航时,程序会字段跳转到相应的URL地址并返回对应的数据信息,运行结果如下图:

10.6  本章小结

  Django为开发者提供了常见的Web应用程序,如会话控制、高速缓存、CSRG防护、消息提示和分页功能。内置的Web应用程序大大优化了网站性能,并且完善了安全防护机制,而且也提高了开发者的开发效率。

  Django提供5种不同的缓存方式,每种缓存方式说明如下:

    1、Memcached:一个高性能的分布式内存对象缓存系统,用于动态网站,以减轻数据库负载。通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高网站的响应速度。使用Memcached需要安装系统服务器,Django通过python-memcached或pylibmc模块调用Memcached系统服务器,实现缓存读写操作,适合超大型网站使用。

    2、数据库缓存:缓存信息存储在网站数据库的缓存表中,缓存表可以在项目的配置文件中配置,适合大中型网站使用。

    3、文件系统缓存:缓存信息以文本文件格式保存,适合中小型网站使用。

    4、本地内存缓存:Django默认的缓存保存方式,将缓存存放在项目所在系统的内存中,只适用于项目开发测试。

    5、虚拟缓存:Django内置的虚拟缓存,实际上只提供缓存接口,并不能存储缓存数据,只适用于项目开发测试。

  Django为了防护这类攻击,在用户提交表单时,表单会自动加入csrftoken的隐含值,这个隐含值会与网站后台保存的csrftoken进行匹配,只有匹配成功,网站才会处理表单数据。这种防护机制称为CSRF防护,原理如下:

    1、在用户访问网站时,Django在网页的表单中生成一个隐含字段csrftoken,这个值是在服务器端随机生成的。

    2、当用户提交表单时,服务器检验表单的csrftoken是否和自己保存的csrftoken一致,用来判断当前请求是否合法。

    3、如果用户被CSRF攻击并从其他地方发生攻击请求,由于其他地方不可能知道隐藏的csrftoken信息,因此导致网站后台校验csrftoken失败,攻击就被成功防御。

  消息提示必须依赖中间件SessionMiddleware,因为消息提示的引擎默认是SessionStorage,而SessionStorage是在Session的基础上实现的,同时说明了中间件SessionMiddleware为什么设置在MessageMiddleware的前面。

  使用信息提示功能之前,需要了解消息提示的类型,Django提供了5种消息类型,说明如下表所示:

类型 说明
DEBUG 提示开发过程中的相关调式信息
INFO 提示信息,如用户信息
SUCCESS 提示当前操作执行成功
WARNING 警告当前操作存在风险
ERROR 提示当前操作错误

Django提供的5种消息类型

上述代码是Django分页功能的使用方法,根据对象类型可以将代码分为两部分:分页对象p和某分页对象page2,两者说明如下:

函数 说明
object_list 输出被分页的全部数据,即数据列表objects
Count 获取当前被分页的数据总量,即数据列表objects的长度
num_pages 获取分页后的总页数
page_range 将总页数转换成range循环对象
page(number) 获取某一页的数据对象,参数number代表页数
函数 说明
has_previous() 判断当前页数是否存在上一页
previous_page_number() 如果当前页数存在上一页,输出上一页的页数,否则抛出EmptyPage异常
has_next() 判断当前页数是否存在下一页
next_page_number() 如果当前页数存在下一页,输出下一页的页数,否则抛出EmptyPage异常
object_list 输出当前分页的数据信息
start_index() 输出当前分页的第一条数据在整个数据列表的位置,数据位置以1开始计算
end_index() 输出当前分页的最后一条数据在整个数据列表的位置,数据位置以1开始计算

玩转Django2.0---Django笔记建站基础十(二)(常用的Web应用程序)的更多相关文章

  1. 玩转Django2.0---Django笔记建站基础十(一)(常用的Web应用程序)

    第十章 常用的Web应用程序 Django为开发者提供了常见的Web应用程序,如会话控制.高速缓存.CSRF防护.消息提示和分页功能.内置的Web应用程序大大优化了网站性能,并且完善了安全防护机制,而 ...

  2. 玩转Django2.0---Django笔记建站基础十二(Django项目上线部署)

    第十二章 Django项目上线部署 目前部署Django项目有两种主流方案:Nginx+uWsGI+Django或者Apache+uWSGI+Django.Nginx作为服务器最前端,负责接收浏览器的 ...

  3. 玩转Django2.0---Django笔记建站基础十一(二)((音乐网站开发))

    11.5 歌曲排行榜 歌曲排行榜是通过首页的导航链接进入的,按照歌曲的播放次数进行降序显示.从排行榜页面的设计图可以看到,网页实现三个功能:网页顶部搜索.歌曲分类筛选和歌曲信息列表,其说明如下: 1. ...

  4. 玩转Django2.0---Django笔记建站基础九(二)(Auth认证系统)

    9.4 设置用户权限 用户权限主要是对不同的用户设置不同的功能使用权限,而每个功能主要以模型来划分.以9.3节的MyDjango项目为例,在Admin后台管理系统可以查看并设置用户权限,如下图: 用户 ...

  5. django笔记 - 建站

    1,建站步骤:1)django-admin.exe startproject mysite 创建完后的目录结构: - mysite # 对整个程序进行配置 - init - settings # 配置 ...

  6. 玩转Django2.0---Django笔记建站基础十三(第三方功能应用)

    第13章 第三方功能应用 在前面的章节中,我们主要讲述Django框架的内置功能以及使用方法,而本章主要讲述Django的第三方功能应用以及使用方法.通过本章的学习,读者能够在网站开发过程中快速开发网 ...

  7. 玩转Django2.0---Django笔记建站基础六(模型与数据库)

    第六章 模型与数据库 Django对各种数据库提供了很好的支持,包括:PostgreSQL.MySQL.SQLite和Oracle,而且为这些数据库提供了统一的调用API,这些API统称为ORM框架. ...

  8. 玩转Django2.0---Django笔记建站基础五(模板)

    第五章 模板 Django作为web框架,需要一种很便利的方法去动态地生成HTML网页,因此有了模板这个概念.模板包含所需HTML的部分代码以及一些特殊语法 Django可以配置一个或多个模板引擎(甚 ...

  9. 玩转Django2.0---Django笔记建站基础四(视图)

    第四章 视图 4.1 探究视图 一.视图说明 视图(View)是Django的MTV架构模式的V部分,主要负责处理用户请求和生成相应的相应部分,然后在页面或其它类型文档中显示.也可以理解为视图是MVC ...

随机推荐

  1. ASP.NET MVC4.0+EF+LINQ+bui+bootstrap+网站+角色权限管理系统(2)

    创建公共分页参数类Common/GridPager.cs using System; using System.Collections.Generic; using System.Linq; usin ...

  2. 2018-8-10-C#-写系统日志

    title author date CreateTime categories C# 写系统日志 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 17:23:3 ...

  3. 2018-11-9-win2d-CanvasCommandList-使用方法

    title author date CreateTime categories win2d CanvasCommandList 使用方法 lindexi 2018-11-9 20:8:4 +0800 ...

  4. dotnet 获取用户设备安装了哪些 .NET Framework 框架

    从注册表可以拿到当前用户安装的 .NET Framework 版本,本文告诉大家如何解析这些信息 在注册表的当前设备的 SOFTWARE\Microsoft\NET Framework Setup\N ...

  5. 爬虫工程师的unidbg入门教程

    现在很多的app使用了so加密,以后会越来越多.爬虫工程师可能会直接逆向app,看java代码,完成java层的算法破解,但是如果遇到so该怎么办呢?可能你会直接破解so,但是真的会有很多爬虫工程师会 ...

  6. SSI(服务器端嵌入)

    简介 SSI(服务器端嵌入)是一组放在 HTML 页面中的指令,当服务器向客户端访问提供这些页面时,会解释执行这些指令.它们能为已有的 HTML 页面添加动态生成内容,不需要通过 CGI 程序来或其他 ...

  7. 对EntityViewInfo的理解

    1,EntityViewInfo常常用作bos中接口参数,来做查询用,其中包含了FilterInfo(过滤).Selector(指定属性)以及Sorter(排序)   SelectorItemColl ...

  8. PSR-1之PHP代码文件必须以不带BOM的UTF-8编码

    BOM——Byte Order Mark,就是字节序标记 在UCS 编码中有一个叫做”ZERO WIDTH NO-BREAK SPACE“的字符,它的编码是FEFF.而FFFE在UCS中是不存在的字符 ...

  9. DEVOPS技术实践_01:jenkins集成平台

    一.准备环境 准备三台机器 角色 IP地址 用户名 密码 jenkins-master   172.25.254.130    admin   meiyoumima gitlab 172.25.254 ...

  10. 大白话讲解Spring的@bean注解

    1.Spring注解分类 从广义上Spring注解可以分为两类: 一类注解是用于注册Bean 假如IOC容器就是一间空屋子,首先这间空屋子啥都没有,我们要吃大餐,我们就要从外部搬运食材和餐具进来.这里 ...