【Django 笔记】模板(2)--CSRF、验证码、URL反向解析

笔记主要基于官方文档,从中提取要点和记录笔记,关键处包含了官方文档链接。详见官方文档。

官方文档:Django documentation 

博客推荐:Django2.2教程

官方文档视图层:模板层

目录

1.CSRF

1.1.基本使用

1.2.装饰器 decorator

2.验证码

3.URL 的反向解析

总结


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

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.htmlhttps://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,这是主要目的。

使用反向解析

  1. 应用的urls.py的 urlpatterns前面,写上 app_name = '应用名'
  2. 给应用中的每一个视图url写上参数 name ,比如:path('index', views.index, name='index'), ;(这步应该作为习惯操作)
  3. 项目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-----

发布了50 篇原创文章 · 获赞 10 · 访问量 6591

猜你喜欢

转载自blog.csdn.net/qq_23996069/article/details/104889157