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开始计算

最新文章

  1. 基于webdriver的jmeter性能测试-Eclipse+Selenium+JUnit生成jar包
  2. python 之 Django 基础篇
  3. 号外!GNOME 3.22 正式发布喽!!!
  4. [转]java.lang.OutOfMemoryError:GC overhead limit exceeded
  5. Intent官方教程(2)Intent的两种类型
  6. SQL Server 2005 中的同义词
  7. dom4j解析器 基于dom4j的xpath技术 简单工厂设计模式 分层结构设计思想 SAX解析器 DOM编程
  8. 个人收藏的flex特效网址【经典中的极品】
  9. 史上最全的css hack
  10. 惠普 hpacucli工具使用
  11. mysql安装常见问题(系统找不到指定的文件、发生系统错误 1067 进程意外终止)
  12. windows平台python 2.7环境编译安装zbar
  13. 通用table样式
  14. ptrdiff_t 和 size_t
  15. python 数据类型之集合
  16. 升级WIN10 (9879)后IE无响应的解决办法
  17. mybatis之级联关系(一对一、一对多)
  18. IDEA分享项目到GitHub出现Could not read from remote repository
  19. 【代码审计】QYKCMS_v4.3.2 任意文件上传漏洞分析
  20. Codeforces 441C Valera and Tubes

热门文章

  1. Canvas学习实践:一款简单的动画游戏
  2. vue-learning:17- js - methods
  3. import()函数
  4. Codeforces Round #587 C. White Sheet(思维+计算几何)
  5. next-i18next 常见Bug记录
  6. op挂载摄像头
  7. 22.文本框验证和外部url的调用
  8. jsp页面获取当前系统时间
  9. docker常用命令(不包括run和build)
  10. 【题解】CF742E (二分图+构造)