Django给表单添加honeypot验证增加安全性


Posted in Python onMay 06, 2021

如果你的网站中允许匿名用户通过POST方式提交表单, 比如用户注册表, 评论表或者留下用户联系方式的表单,你一定要防止机器人或爬虫程序恶意提交大量的垃圾数据到你的数据库中。这种情况不是可能会发生,而是一定会发生。一种解决这种问题的方式就是在表单中加入人机交互验证码(CAPTCHA), 另一种方式就是在表单中加入honeypot隐藏字段,然后在视图中对隐藏字段的值进行验证。两种验证方式的目的都是一样,防止机器人或程序通过伪装成人来提交数据。今天我们就来详细介绍下如何在表单中添加honeypot增加安全性。

Honeypot的工作原理

Honeypot又名蜜罐,其实本质上是种陷阱。我们在表单中故意通过CSS隐藏一些字段, 这些字段一般人是不可见的。然而机器人或程序会以为这些字段也是必需的字段(required), 所以会补全后提交表单,这就中了我们的陷阱。在视图中我们可以通过装饰器对用户提交的表单数据进行判断,来验证表单的合法性。比如honeypot字段本来应该为空的,现在居然有内容了,显然这是机器人或程序提交的数据,我们可以拒绝其请求。

Django中如何实现表单honeypot验证?

Django表单中添加honeypot,一共分两步:

1. 编写模板标签(templatetags),在包含模板的表单中生成honeypot字段。

2. 编写装饰器(decorators.py), 对POST请求发送来的表单数据进行验证。

由于honeypot的功能所有app都可以用到,我们创建了一个叫common的app。整个项目的目录结构如下所示。只有标蓝色的4个文件,是与honeypot相关的。

Django给表单添加honeypot验证增加安全性

编写模板标签

我们在common目录下新建templatetags目录(包含一个空的__init__.py),然后在新建common_tags_filters.py, 添加如下代码。

from django import template
from django.conf import settings
from django.template.defaultfilters import stringfilter


register = template.Library()


# used to render honeypot field
@register.inclusion_tag('common/snippets/honeypot_field.html')
def render_honeypot_field(field_name=None):
    """
        Renders honeypot field named field_name (defaults to HONEYPOT_FIELD_NAME).
    """
    if not field_name:
        field_name = getattr(settings, 'HONEYPOT_FIELD_NAME', 'name1')
    value = getattr(settings, 'HONEYPOT_VALUE', '')
    if callable(value):
        value = value()
    return {'fieldname': field_name, 'value': value}

我们现在来看下上面这段代码如何工作的。我们创建了一个名为render_honeypot_field的模板标签,用于在模板中生成honeypot字段。honeypot字段名是settings.py里HONEYPOT_FIELD_NAME,如果没有此项设置,默认值为name1。honeypot字段的默认值是HONEYPOT_VALUE, 如果没有此项设置,默认值为空字符串''。然后这个函数将fieldname和value传递给如下模板片段。

# common/snippets/honeypot_field.html

<div class="form-control" style="display: none;">
        <label><input type="text" name="{{ fieldname }}" value="{{ value }}" />
    </label>
</div>

在Django模板的表单中生成honeypot字段只需按如下操作:

{% load common_tags_filters %}
{% load static %}

<form method="post" action="">
     {% csrf_token %}
    {% render_honeypot_field %}
    {% form.as_p %}
</form>

编写装饰器

在common文件下新建decorators.py, 添加如下代码。我们编写了check_honeypot和honeypot_exempt两个装饰器,前者给需要对honeypot字段进行验证的视图函数使用,后者给不需要对honeypot字段进行验证的视图函数使用。

#common/decorators.py

from functools import wraps
from django.conf import settings
from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect
from django.template.loader import render_to_string
from django.contrib.auth.decorators import user_passes_test


def honeypot_equals(val):
    """
        Default verifier used if HONEYPOT_VERIFIER is not specified.
        Ensures val == HONEYPOT_VALUE or HONEYPOT_VALUE() if it's a callable.
    """
    expected = getattr(settings, 'HONEYPOT_VALUE', '')
    if callable(expected):
        expected = expected()
    return val == expected


def verify_honeypot_value(request, field_name):
    """
        Verify that request.POST[field_name] is a valid honeypot.
        Ensures that the field exists and passes verification according to
        HONEYPOT_VERIFIER.
    """
    verifier = getattr(settings, 'HONEYPOT_VERIFIER', honeypot_equals)
    if request.method == 'POST':
        field = field_name or settings.HONEYPOT_FIELD_NAME
        if field not in request.POST or not verifier(request.POST[field]):
            response = render_to_string('common/snippets/honeypot_error.html',
                                    {'fieldname': field})
            return HttpResponseBadRequest(response)


def check_honeypot(func=None, field_name=None):
    """
        Check request.POST for valid honeypot field.
        Takes an optional field_name that defaults to HONEYPOT_FIELD_NAME if
        not specified.
    """
    # hack to reverse arguments if called with str param
    if isinstance(func, str):
        func, field_name = field_name, func

    def wrapper(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            response = verify_honeypot_value(request, field_name)
            if response:
                return response
            else:
                return func(request, *args, **kwargs)
        return inner

    if func is None:
        def decorator(func):
            return wrapper(func)
        return decorator

    return wrapper(func)


def honeypot_exempt(func):
    """
        Mark view as exempt from honeypot validation
    """
    # borrowing liberally from django's csrf_exempt
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper.honeypot_exempt = True
    return wrapper

上面代码最重要的就是verify_honeypot_value函数了。如果用户通过POST方式提交的表单里没有honeypot字段或该字段的值不等于settings.py中的默认值,则验证失败并返回如下错误:

# common/snippets/honeypot_error.html

<!DOCTYPE html>
<html lang="en">
    <body>
    <h1>400 Bad POST Request</h1>
    <p>We have detected a suspicious request. Your request is aborted.</p>
    </body>
</html>

定义好装饰器后,我们对需要处理POST表单的视图函数加上@check_honeypot就行了,是不是很简单?

from common.decorators import check_honeypot


@check_honeypot
def signup(request):
    if request.method == "POST":
        form = SignUpForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            return HttpResponseRedirect(reverse('users:profile'))
    else:
        form = SignUpForm()

    return render(request, "users/signup.html", {"form": form, })

参考

本文核心代码参考了James Sturk的Django-honeypot项目。原项目地址如下所示:

https://github.com/jamesturk/django-honeypot/

以上就是Django给表单添加honeypot验证增加安全性的详细内容,更多关于Django 添加honeypot验证的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python使用百度API上传文件到百度网盘代码分享
Nov 08 Python
Python定时执行之Timer用法示例
May 27 Python
Python实现更改图片尺寸大小的方法(基于Pillow包)
Sep 19 Python
Python实现迭代时使用索引的方法示例
Jun 05 Python
Python 使用Numpy对矩阵进行转置的方法
Jan 28 Python
python django生成迁移文件的实例
Aug 31 Python
关于keras.layers.Conv1D的kernel_size参数使用介绍
May 22 Python
Python文件操作模拟用户登陆代码实例
Jun 09 Python
tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this T
Jun 22 Python
完美解决keras 读取多个hdf5文件进行训练的问题
Jul 01 Python
用Python实现定时备份Mongodb数据并上传到FTP服务器
Jan 27 Python
Python爬取英雄联盟MSI直播间弹幕并生成词云图
Jun 01 Python
Django利用AJAX技术实现博文实时搜索
May 06 #Python
python 如何获取页面所有a标签下href的值
May 06 #Python
Python中常见的导入方式总结
May 06 #Python
Python基础之hashlib模块详解
May 06 #Python
用Python爬虫破解滑动验证码的案例解析
python本地文件服务器实例教程
python字符串常规操作大全
You might like
PHP获取数组中重复最多的元素的实现方法
2014/11/11 PHP
使用GDB调试PHP代码,解决PHP代码死循环问题
2015/03/02 PHP
php5.4以上版本GBK编码下htmlspecialchars输出为空问题解决方法汇总
2015/04/03 PHP
针对PHP开发安全问题的相关总结
2019/03/22 PHP
JQuery中的ready函数冲突的解决方法
2010/05/17 Javascript
把jQuery的类、插件封装成seajs的模块的方法
2014/03/12 Javascript
jQuery的:parent选择器定义和用法
2014/07/01 Javascript
JavaScript内存管理介绍
2015/03/13 Javascript
JS和css实现检测移动设备方向的变化并判断横竖屏幕
2015/05/25 Javascript
jQuery模拟物体自由落体运动(附演示与demo源码下载)
2016/01/21 Javascript
js简单获取表单中单选按钮值的方法
2016/08/23 Javascript
JS用斜率判断鼠标进入DIV四个方向的方法
2016/11/07 Javascript
JavaScript实现淘宝京东6位数字支付密码效果
2018/08/18 Javascript
详解Node.js amqplib 连接 Rabbit MQ最佳实践
2019/01/24 Javascript
JavaScript解析机制与闭包原理实例详解
2019/03/08 Javascript
vue实现滑动切换效果(仅在手机模式下可用)
2020/06/29 Javascript
在vue中高德地图引入和轨迹的绘制的实现
2019/10/11 Javascript
JavaScript 如何计算文本的行数的实现
2020/09/14 Javascript
Python中Collection的使用小技巧
2014/08/18 Python
python计算两个矩形框重合百分比的实例
2018/11/07 Python
python实现名片管理器的示例代码
2019/12/17 Python
如何使用python传入不确定个数参数
2020/02/18 Python
Python多线程操作之互斥锁、递归锁、信号量、事件实例详解
2020/03/24 Python
基于Python把网站域名解析成ip地址
2020/05/25 Python
canvas 绘图时位置偏离的问题解决
2020/09/16 HTML / CSS
Redbubble法国:由独立艺术家设计的独特产品
2019/01/08 全球购物
英国门销售网站:Green Tree Doors
2020/01/07 全球购物
大学生求职自荐信
2013/12/12 职场文书
运动会100米解说词
2014/01/23 职场文书
商务英语专业求职信范文
2014/01/28 职场文书
师范学院美术系毕业生自我鉴定
2014/01/29 职场文书
《奇妙的国际互联网》 教学反思
2014/02/25 职场文书
运动会口号大全
2014/06/07 职场文书
学校政风行风自查自纠报告
2014/10/21 职场文书
2016个人廉洁自律承诺书
2016/03/25 职场文书
Redis源码阅读:Redis字符串SDS详解
2021/07/15 Redis