笔记主要基于官方文档,从中提取要点和记录笔记,关键处包含了官方文档链接。详见官方文档。
官方文档:Django documentation
博客推荐:Django2.2教程
官方文档视图层:模板层
目录
1.CSRF
官方文档:跨站请求伪造 CSRF 保护
更多安全相关文档:安全
1.1.基本使用
CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
创建Django项目时,会默认启用CSRF中间件。若没有开启,可在setting.py的MIDDLEWARE设置中添加'django.middleware.csrf.CsrfViewMiddleware', 。
CSRF通常是针对POST方法的。Django为我们提供了防范CSRF攻击的机制。基本使用如下:
针对表单:
在含有POST表单的模板中,需要在其<form>
表单元素内部添加csrf_token
标签,如下所示:
<form action="" method="post">
{% csrf_token %}
....
</form>
这样,当表单数据通过POST方法,发送到后台服务器的时候,除了正常的表单数据外,还会携带一个CSRF令牌随机字符串,用于进行csrf验证。
AJAX中不能像form表单中那样携带{% csrf_token %}
令牌。若Django服务器接收的是一个通过AJAX发送过来的POST请求的话,就比较麻烦。
Django官方提供的解决方案是,在前端模版的JavaScript代码处,添加下面的代码,这样Ajax的POST方法就能带上CSRF需要的令牌,它依赖Jquery库,必须提前加载Jquery。(详见官方文档)
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// 这些HTTP方法不要求CSRF包含
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
1.2.装饰器 decorator
(1)单独指定csrf验证需要
有时候,我们在全站上关闭了CSRF功能,但某些视图还需要CSRF防御,那怎么办呢?Django提供了一个 csrf_protect(view) 装饰器。
csrf_protect(view) usage:
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
以上,虽然全站关掉了csrf,但是my_view视图依然进行csrf验证。
注:如果使用基于类的视图,可以参考 Decorating class-based views 装饰基于类的视图.。
(2)单独指定忽略csrf验证
相反的,在全站开启CSRF机制的时候,有些视图我们并不想开启CSRF机制。这怎么办呢?这就需要使用Django为我们提供的csrf_exempt(view)
装饰器了(此时,安全问题要另外处理),用法:
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
@csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
(3)确保csrf令牌被设置
Django还提供了一个装饰器,确保被装饰的视图在返回页面时同时将csrf令牌一起返回。
这个装饰器是:ensure_csrf_cookie(view):
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse
@ensure_csrf_cookie
def my_view(request):
return HttpResponse('Hello world')
在实际开发中,有些页面需要验证登录才能访问和进行操作,这时可以设计一个登录装饰器,在装饰器中进行登录验证。减少代码量和提高逻辑性。
2.验证码
在用户注册、登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。
手动实现验证码(会用即可,网上实现方法很多)
接下来的代码不要求手动写出来,因为这种代码在网上可以搜到很多。
1)安装包Pillow。
pip install Pillow
也可以指定安装版本。以下代码中用到了Image、ImageDraw、ImageFont对象及方法。
pillow:https://pillow.readthedocs.io/en/latest/index.html
Pillow相关博客:https://www.cnblogs.com/linyouyi/p/11429511.html、https://blog.csdn.net/wzyaiwl/article/details/89023729
2)在views.py文件中,创建视图verify_code。
- 提示1:随机生成字符串后存入session中,用于后续判断。
- 提示2:视图返回mime-type为image/png。
from PIL import Image, ImageDraw, ImageFont from django.utils.six import BytesIO ... def verify_code(request): #引入随机函数模块 import random #定义变量,用于画面的背景色、宽、高 bgcolor = (random.randrange(20, 100), random.randrange( 20, 100), 255) width = 100 height = 25 #创建画面对象 im = Image.new('RGB', (width, height), bgcolor) #创建画笔对象 draw = ImageDraw.Draw(im) #调用画笔的point()函数绘制噪点 for i in range(0, 100): xy = (random.randrange(0, width), random.randrange(0, height)) fill = (random.randrange(0, 255), 255, random.randrange(0, 255)) draw.point(xy, fill=fill) #定义验证码的备选值 str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0' #随机选取4个值作为验证码 rand_str = '' for i in range(0, 4): rand_str += str1[random.randrange(0, len(str1))] #构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont” font = ImageFont.truetype('FreeMono.ttf', 23) #构造字体颜色 fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255)) #绘制4个字 draw.text((5, 2), rand_str[0], font=font, fill=fontcolor) draw.text((25, 2), rand_str[1], font=font, fill=fontcolor) draw.text((50, 2), rand_str[2], font=font, fill=fontcolor) draw.text((75, 2), rand_str[3], font=font, fill=fontcolor) #释放画笔 del draw #存入session,用于做进一步验证 request.session['verifycode'] = rand_str #内存文件操作 buf = BytesIO() #将图片保存在内存中,文件类型为png im.save(buf, 'png') #将内存中的图片数据返回给客户端,MIME类型为图片png return HttpResponse(buf.getvalue(), 'image/png')
3)打开 应用/urls.py文件,配置url。
path('verify_code', views.verify_code, name='verify_code'), # 产生验证码图片
4)运行服务器,在浏览器中输入如下网址。就可以看到生成的验证码。
http://127.0.0.1:8000/verify_code
需要校验时,在视图中关键代码如下:
# 获取用户输入验证码 vcode1 = request.POST.get('vcode') # 获取session中保存的验证码 vcode2 = request.session.get('verifycode') # 进行验证码校验 if vcode1 != vcode2: # 验证码错误 return redirect('/login') # 检验失败,重定向到一个页面
3.URL 的反向解析
官方文档:URL调度器(处理请求、路径转换器、使用正则表达式、URLconf匹配URL中的哪些部分、包含其它的URLconfs(路由转发)、URL 的反向解析 等)
博客:https://www.liujiangblog.com/course/django/136
在实际的Django项目中,经常需要获取某条URL,为生成的内容配置URL链接。
这最好不要硬编码 URL(费力、不能扩展、容易出错)。也不要将临时机制生成的 URL 与URLconf描述的设计的URL一样,这会导致 URL 随着时间的推移变得过时。
我们需要一种安全、可靠、自适应的机制,当修改URLconf中的代码后,无需在项目源码中大范围搜索、替换失效的硬编码URL。Django提供了一种解决方案,只需在URL中提供一个name参数,并赋值一个你自定义的、好记的、直观的字符串。
在需要解析URL的地方,对于不同层级,Django提供了不同的工具用于URL反查:
- 在模板里:使用
url
模板标签。(也就是写前端网页时)- 在 Python 编码:使用
reverse()
函数。(也就是写视图函数等情况时)- 在与 Django 模型实例的 URL 处理相关的高级代码中,使用:
get_absolute_url()
方法。(也就是在模型model中)
(1)name参数
通过name参数,可以反向解析URL、反向URL匹配、反向URL查询或者简单的URL反查。
例子如下:
URLconf:(注意name参数:name='news-year-archive')
from django.urls import path from . import views # 如果是app的urls,这里加上:app_name = '应用名' urlpatterns = [ #... path('articles/<int:year>/', views.year_archive, name='news-year-archive'), #... ]
根据以上,与 2020年 相对应的 URL 是
/articles/2020/
。
可以使用以下方式在模板代码中来获取它们:
硬编码的链接(只能访问/articles/2012):<br/> <a href="/articles/2012/">2012 Archive</a><br/> 解析动态生成链接(能访问 "articles/<int:year>/" 格式的路径):<br/> <a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a> {# Or with the year in a template context variable: #} <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
或在 Python 代码里:from django.urls import reverse,再使用
reverse()
函数from django.http import HttpResponseRedirect from django.urls import reverse def redirect_to_year(request): # ... year = 2020 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
起到核心作用的是通过
name='news-year-archive'
为url起了一个可以被引用的名称。
两个不同的app,若在各自的urlconf中为某一条url取了相同的name,就会带来麻烦。可以用命名空间解决问题。
(2)URL namespaces
URL命名空间可以保证即使不同的app使用相同的URL名称,也能反查到唯一的URL。推荐每个自定义应用使用带命名空间的URL。
以Django文档的例子为例,实现命名空间的做法很简单,在urlconf文件中添加app_name = 'polls'
和namespace='author-polls'
这种类似的定义。
推荐app_name = 'polls'
的方式。例子可参考官方文档,点击标题可见。
(3)URL命名空间和include的URLconf
.可以通过两种方式指定include的URLconf 的 Application namespaces。
第一种(推荐):
在include的URLconf模块中,设置app_name
属性,其与urlpatterns属性同级别。必须将实际模块或模块的字符串引用传递到 include()
,即作为 include()的参数
,而不是urlpatterns本身的列表。
polls/urls.py:app_name = 'polls'
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
...
]
urls.py
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
]
此时,polls.urls中定义的URL将具有应用名称空间polls(对应app_name = 'polls')。
第二种,见官方文档。
总结
总之,反向解析可以根据path的配置动态的生成url,这是主要目的。
使用反向解析:
- 在应用的urls.py的 urlpatterns前面,写上 app_name = '应用名' ;
- 给应用中的每一个视图url写上参数 name ,比如:path('index', views.index, name='index'), ;(这步应该作为习惯操作)
- 在项目urls.py中 include 一个应用的urls.py时,include函数写上参数 namespace='指定的名字' ;(Django2.x中,其实不写namespace也可以反向解析)
在模板中:可以使用 {% url ' 应用名(即app_name ) : 视图名(即name) ' %} 等格式反向解析,动态生成链接,
如:<a href="{% url 'booktest:index' %}">首页</a>
在视图中:导入from django.urls import reverse,
使用
reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)[源代码]
(viewname
可以是一个url模式名称或一个可调用的视图对象,如 '应用名:视图名')
。
即可动态生成链接,如下:
from django.urls import reverse
...
# /test_redirect
def test_redirect(request):
# 重定向到/index
# return redirect('/index') # 硬编码写死
# 动态生成链接
url = reverse('booktest:index')
return redirect(url)
-----end-----