LogoLogo
  • README
  • 前端编程
    • 01 Node JS
    • 02-ES6详解
    • 03-NPM详解
    • 04-Babel详解
    • 05-前端模块化开发
    • 06-WebPack详解
    • 07-Vue详解
    • 08-Git详解
    • 09-微信小程序
  • 人工智能
    • 机器学习
      • 二次分配问题
      • 非负矩阵
      • 概率潜在语义分析
      • 概率图模型
      • 集成学习
      • 降维
      • 距离度量
      • 决策树
      • 逻辑回归
      • 马尔可夫决策过程
      • 马尔可夫链蒙特卡洛法
      • 朴素贝叶斯法
      • 谱聚类
      • 奇异值分解
      • 潜在狄利克雷分配
      • 潜在语义分析
      • 强化学习
      • 社区算法
      • 时间序列模型
      • 特征工程
      • 条件随机场
      • 图论基础
      • 线性分类
      • 线性回归
      • 信息论中的熵
      • 隐马尔科夫模型
      • 支持向量机
      • 主成分分析
      • EM算法
      • Hermite 矩阵的特征值不等式
      • k-means聚类
      • k近邻法
      • PageRank算法
    • 深度学习
      • Pytorch篇
        • 01-线性模型
        • 02-梯度下降法
        • 03-反向传播
        • 04-pytorch入门
        • 05-用pytorch实现线性回归
        • 06-logistic回归
        • 07-处理多维特征的输入
        • 08-加载数据集
        • 09-多分类问题
        • 10-卷积神经网络
        • 11-循环神经网络
    • 图神经网络
      • 图神经网络笔记01
        • 01-图(Graphs)的结构
        • 02-网络的性质和随机图模型
        • 03-网络工具
        • 04-网络中的主题和结构角色
        • 05-网络中的社区结构
      • 图神经网络笔记02
        • 01-深度学习引言
        • 02-神经网络基础
        • 03-卷积神经网络
        • 04-图信号处理与图卷积神经网络
        • 05-GNN的变体与框架-
        • [06-Google PPRGo 两分钟分类千万节点的最快GNN](人工智能/图神经网络/图神经网络笔记02/06-Google%20PPRGo 两分钟分类千万节点的最快GNN.md)
        • 07-序列模型
        • 08-变分自编码器
        • 09-对抗生成网络
  • 日常记录
    • 健身日记
    • 面经记录
    • 自动生成Summary文件
  • 实战项目
    • 谷粒商城
      • 00-项目概述
      • 01-分布式基础-全栈开发篇
      • 02-分布式高级-微服务架构篇
      • 03-高可用集群-架构师提升篇
  • 数据库
    • MySQL笔记
      • 01-MySQL基础篇
      • 02-MySQL架构篇
      • 03-MySQL索引及调优篇
      • 04-MySQL事务篇
      • 05-MySQL日志与备份篇
    • Redis笔记
      • 01-Redis基础篇
      • 02-Redis高级篇
    • 02-Redis篇
  • 算法笔记
    • 01-算法基础篇
    • 02-算法刷题篇
  • 职能扩展
    • 产品运营篇
  • Go编程
    • 01-Go基础
      • 01-Go基础篇
  • Java编程
    • 01-Java基础
      • 01-Java基础篇
      • 02-多线程篇
      • 03-注射与反解篇
      • 04-JUC并发编程篇
      • 05-JUC并发编程与源码分析
      • 06-JVM原理篇
      • 07-Netty原理篇
      • 08-设计模式篇
    • 02 Java Web
      • 01-Mybatis篇
      • 01-Mybatis篇(新版)
      • 02-Spring篇
      • 02-Spring篇(新版)
      • 03-SpringMVC篇
      • 04-MybatisPlus篇
    • 03-Java微服务
      • 01-SpringBoot篇
      • 01-SpringBoot篇(新版)
      • 02-SpringSecurity篇
      • 03-Shiro篇
      • 04-Swagger篇
      • 05-Zookeeper篇
      • 06-Dubbo篇
      • 07-SpringCloud篇
      • 08-SpringAlibaba篇
      • 09-SpringCloud篇(新版)
    • 04-Java中间件
      • 数据库篇
        • 01-分库分表概述
        • 02-MyCat篇
        • 03-MyCat2篇
        • 04-Sharding-jdbc篇
        • 05-ElasticSearch篇
      • 消息中间件篇
        • 01-MQ概述
        • 02-RabbitMQ篇
        • 03-Kafka篇
        • 04-RocketMQ篇
        • 05-Pulsar篇
    • 05-扩展篇
      • Dubbo篇
      • SpringBoot篇
      • SpringCloud篇
    • 06-第三方技术
      • 01-CDN技术篇
      • 02-POI技术篇
      • 03-第三方支付技术篇
      • 04-第三方登录技术篇
      • 05-第三方短信接入篇
      • 06-视频点播技术篇
      • 07-视频直播技术篇
    • 07-云原生
      • 01-Docker篇
      • 02-Kubernetes篇
      • 03-Kubesphere篇
  • Linux运维
    • 01-Linux篇
    • 02-Nginx篇
  • Python编程
    • 01-Python基础
      • 01.配置环境
      • 02.流程控制
      • 03.数值
      • 04.操作符
      • 05.列表
      • 06.元祖
      • 07.集合
      • 08.字典
      • 09.复制
      • 10.字符串
      • 11.函数
      • 12.常见内置函数
      • 13.变量
      • 14.异常和语法错误
      • 15.时间和日期
      • 16.正则表达式
    • 02 Python Web
      • flask篇
        • 01.前言
        • 02.路由
        • 03.模板
        • 04.视图进阶
        • 05.flask-sqlalchemy
        • 06.表单WTForms
        • 07.session与cookie
        • 08.上下文
        • 09.钩子函数
        • 10.flask 信号
        • 11.RESTFUL
        • 13.flask-mail
        • 14.flask+celery
        • 15.部署
        • 16.flask-login
        • 17.flask-cache
        • 18.flask-babel
        • 19.flask-dashed
        • 20.flask-pjax
        • 21.flask上传文件到第三方
        • 22.flask-restless
        • 23.flask-redis
        • 24.flask-flash
        • 25.消息通知
        • 26.分页
    • 03-Python数据分析
      • Matplotlib
      • Numpy
      • Pandas
      • Seaborn
    • 04-Python爬虫
      • 1.准备工作
      • 2.请求模块的使用
      • 3.解析模块的使用
      • 4.数据存储
      • 5.识别验证码
      • 6.爬取APP
      • 7.爬虫框架
      • 8.分布式爬虫
由 GitBook 提供支持
在本页
  • 作用
  • 安装
  • 导入
  • 创建表单
  • 演示
  • 问题
  • 表单定义
  • 获取数据
  • 常用字段
  • 验证表单
  • 自定义表单验证器
  • namefield验证器
  • 把验证器编写成单独的函数
  • 使用WTForms渲染模版
  • 安全表单
  • 文件上传
  • jQuery 利用 FormData 上传文件
  • HTML
  • 获取文件列表
  • jQuery上传文件
  • 注意上传文件与传递参数的细节
  • 验证上传的文件
  • 验证码
  • CSRF 保护
  • 为什么需要 CSRF?
  • SCRECT_KEY随机生成
  • 实现
  • AJAX

这有帮助吗?

在GitHub上编辑
  1. Python编程
  2. 02 Python Web
  3. flask篇

06.表单WTForms

作用

  • 带有 csrf 令牌的安全表单。

  • 全局的 csrf 保护。

  • 支持验证码(Recaptcha)。

  • 与 Flask-Uploads 一起支持文件上传。

  • 国际化集成。

安装

pip install Flask-WTF

导入

from flask import Flask
from flask_wtf import Form
from wtforms import StringField
from wtforms.validators import DataRequired

创建表单

演示

app.py

from flask import Flask,render_template,request
from flask_wtf import Form
from wtforms import StringField
from wtforms.validators import DataRequired,Length,EqualTo

app = Flask(__name__)
app.config["SECRET_KEY"] = "12345678"

class TestForm(Form):
    name = StringField("name",validators=[DataRequired(),Length(min=3,max=10,message="用户名长度必须在3到10位之间")])
    password1 = StringField("password1",validators=[Length(min=6,max=10)])
    # EqualTo指定与之保持相同值的字段
    password2 = StringField("password2",validators=[Length(min=6,max=10),EqualTo("password1")])

@app.route('/login/',methods=["GET","POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")
    else:
        form = TestForm(request.form)
        if form.validate(): # 如果通过验证,返回为True,否则False
            return "{}".format(form.data)
        else:
            return "{}".format(form.errors) # 打印错误信息

if __name__ == '__main__':
    app.run()

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
</head>
<body>
<form>
    <input type="text" name="name"> <br>
    <input type="text" name="password1"> <br>
    <input type="text" name="password2"> <br>
    <button id="submit">提交</button>
</form>
<script>
    $(function () {
        $("#submit").click(function (event) {
            {# 阻止默认事件发生#}
            event.preventDefault()
            var name = $("form>input[name='name']").val()
            var password1 = $("input[name='password1']").val()
            var password2 = $("input[name='password2']").val()
            $.ajax({
              type: 'POST',
              url: "/login/",
              data: {
                  "name":name,
                  "password1":password1,
                  "password2":password2,
              },
              success: function (data) {
                  console.log("post 请求成功")
              },
            });
        })
    })
</script>
</body>
</html>

问题

添加app.config["SECRET_KEY"] = "xxxx"即可解决如下问题

RuntimeError: A secret key is required to use CSRF.

表单定义

  • label:字段的名字

  • default:默认

  • validators:字段的验证序列

  • description:字段的描述

  • choices:SelectField和他的子类有的字段,选择框,多选一

class UserAdminForm(FlaskForm):
    username = StringField(label='用户名', validators=[DataRequired(),Length(4,20)])
    password_hash = PasswordField(label='密码',validators=[DataRequired(),Length(4,20)])
    limit = SelectField(label='用户权限',
                        choices=[('guest', '所有权限'),
                                 ('operation', '可读可写不可删除'),
                                 ('management', '可读不可写')],
                        default='guest')  # 权限

获取数据

由get请求发送过来的数据,通过request.args.get("属性名称")进行获取,由post请求发送过来的数据,如果传递过来的为文件,通过request.fiels,如果是数据,通过request.form。

常用字段

from wtforms import *
表单域类型
描述

StringField

文本框

TextAreaField

多行文本框

PasswordField

密码输入框

HiddenField

隐藏文本框

DateField

接收给定格式datetime.date型的文本框

DateTimeField

接收给定格式datetime.datetime的文本框

IntegerField

接收整型的文本框

DecimalField

接收decimal.Decimal型的文本框

FloadField

接收浮点型的文本框

BooleanField

带有True和False的复选框

RadioField

—组单选框

SelectField

下拉选择框

SelectMultipleField

下拉多选框

FileField

文件上传框

SubmitField

表单提交按钮

FormField

将一个表单作为表单域嵌入到容器表单中

FieldList

给定类型的表单域列表

验证表单

from wtforms.validators import *
验证程序
描述.

Email

验证邮箱地址

DataRequired

验证数据是否真实存在,即不能为空,必须是非空白字符串,否则触发StopValidation错误。

EqualTo

比较两个域的值;在要求输入两次密码进行确认的时候非常有 用

IPAddress

验证|Pv4网络地址

Length

验证输入字符串的长度

NumberRange

验证输入的值在数值范围内

Optional

允许输入为空;忽略额外的验证

Required

验证表单域包含数据

Regexp

验证输入的正则表达式

URL

验证--个URL

AnyOf

验证输入是一组可能值中的--个

NoneOf

验证输入不是一组可能值中的一个

数据发过来时,经过表单验证,因此需要验证器来进行验证,以下对一些常用的内置验证器进行讲解:

  • Email:验证上传的数据是否为邮箱

# 验证邮箱
email = StringField(validators=[Email()])
  • EqualTo:验证上传的数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等

# EqualTo指定与之保持相同值的字段
password_repeat = StringField(validators=[Length(min=6,max=10),EqualTo("password")])
  • InputRequired:原始数据的需要验证。如果不是特殊情况,应该使用InputRequired。

# 验证用户是否输入
username = StringField(validators=[input_required()])
  • Length:长度限制,有min和max两个.值进行限制

username = StringField(validators=[Length(min=3,max=10,message="用户名长度必须在3到10位之间")])
  • NumberRange:数字的区间,有min和max两个值限制,如果处在两个数字之间则满足

# 验证范围
age = IntegerField(validators=[NumberRange(12,18)])
  • Regexp:自定义正则表达式

# 正则表达式
phone = StringField(validators=[Regexp(r'1[34578]\d{9}')])
  • URL:必须要是URL的形式

# url验证
home_page = StringField(validators=[URL()])
  • UUID:验证UUID

# uuid值验证
uuid = StringField(validators=[UUID()])

自定义表单验证器

namefield验证器

class LoginForm(Form):
.....
    # 1234
    captcha = StringField(validators=[Length(4,4)])

    # 自定义验证器
    # valiedate_name
    def validate_captcha(self,field):
        # print(type(field))
        # 字段数据值
        # print(field.data)
        if field.data != "1234":
            raise ValidationError("验证码错误") # 抛出错误信息

通过valiedate_name(其中name为定义的属性),定义一个函数自定义验证属性值是否合法。

把验证器编写成单独的函数

def validate_captcha(form,field):
    # print(type(field))
    # 字段数据值
    # print(field.data)
    if field.data != "1234":
       raise ValidationError("验证码错误")

class LoginForm(Form):
    .....
    # 1234
    captcha = StringField(validators=[validate_captcha,Length(4,4)])

使用WTForms渲染模版

app.py

from flask import Flask,render_template,request
from flask_wtf import Form
from wtforms import StringField,IntegerField,BooleanField,SelectField
from wtforms.validators import InputRequired,NumberRange

app = Flask(__name__)
app.config["SECRET_KEY"] = "12345678"

class SettingsForm(Form):
    username = StringField(label="用户名",validators=[InputRequired()])
    age = IntegerField("年龄",validators=[NumberRange(0,100)])
    remember = BooleanField("记住")
    tags = SelectField("标签",choices=[("1","python"),("2","java"),("3","c++")])

@app.route('/',methods=["GET","POST"])
def settings():
    if request.method == "GET":
        form = SettingsForm(request.form,csrf_enabled=False)
        return render_template("settings.html",form=form)
    else:
        form = SettingsForm(request.form,csrf_enabled=False)
        if form.validate():
            return "{}".format(form.data)
        else:
            return "{}".format(form.errors)

if __name__ == '__main__':
    app.run()

settings.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style type="text/css">
        .username-input{
            background-color: red;
        }
    </style>
</head>
<body>
    <form action="" method="post">
        <table>
            <tbody>
                <tr>
                    <td>{{ form.username.label }}</td>
{#                    <td><input type="text" name="username"/></td>#}
                    <td>{{ form.username(class="username-input") }}</td>
                </tr>
                <tr>
                    <td>{{ form.age.label }}</td>
                    <td>{{ form.age() }}</td>
                </tr>
                <tr>
                    <td>{{ form.remember.label }}</td>
                    <td>{{ form.remember() }}</td>
                </tr>
                <tr>
                    <td>{{ form.tags.label }}</td>
                    <td>{{ form.tags() }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"/></td>
                </tr>
            </tbody>
        </table>
    </form>
</body>
</html>

通过创建WTForms表单类SettingsForm,创建实例化对象form之后,传递给模板。

传递过去的form对象,通过form.属性获取属性值,form.属性值.label获取属性名。

如果想要添加属性class之类的,如下所示:

form.username(class="username-input")

安全表单

无需任何配置,Form是一个带有 CSRF 保护的并且会话安全的表单。

  • 但是如果想要禁用 CSRF 保护,可以这样:

form = Form(csrf_enabled=False)
  • 如果想要全局禁用 CSRF 保护,真的不应该这样做。但是要坚持这样做的话,可以在配置中这样写:

WTF_CSRF_ENABLED = False

为了生成 CSRF 令牌,必须有一个密钥,这通常与Flask 应用密钥一致。如果想使用不同的密钥,可在配置中指定:

WTF_CSRF_SECRET_KEY = 'a random string'

或者

SECRET_KEY = 'a random string'

文件上传

jQuery 利用 FormData 上传文件

<form>
  <input type="file" id="photo" name="photo">
  <button type="button">保存</button>
</form>

但为什么只能选择一个文件??给<input>添加一个multiple属性就可以多选了!

<input type="file" id="photo" name="photo" multiple>

文件上传是Web开发中的重要话题,最直接和简单的方式是通过表单直接提交文件。 Harttle认为,我们引入jQuery来进行异步上传可以获得更好的用户体验。 一方面,在JavaScript中进行异步操作比表单更加灵活; 另一方面,异步上传也避免了上传大文件时的页面长时间卡死。

HTML

一个type=file的<input>就可以让用户来浏览并选择文件, 一般会把输入控件放到一个<form>中,下面的一个简单的表单:

<form enctype="multipart/form-data">
  <input type="file" id="photo" name="photo">
  <button type="button">保存</button>
</form>

其中enctype="multipart/form-data"能够成功编码二进制数据。

但为什么我只能选择一个文件??给<input>添加一个multiple属性就可以多选了!

<input type="file" id="photo" name="photo" multiple>

获取文件列表

上述的<input>将会拥有一个叫files的DOM属性,包含了所选的文件列表(Array)。

$('button').click(function(){
  var $input = $('#photo');
  // 相当于: $input[0].files, $input.get(0).files
  var files = $input.prop('files');
  console.log(files);
});

这个Array中的每一项都是一个File对象,它有下面几个主要属性:

  • name: 文件名,只读字符串,不包含任何路径信息.

  • size: 文件大小,单位为字节,只读的64位整数.

  • type: MIME类型,只读字符串,如果类型未知,则返回空字符串.

jQuery上传文件

这是XMLHttpRequest Level 2提供的FormData对象可以帮助进行二进制文件的 multipart/form-data编码:

<script>
    $(function () {
        $('button').click(function () {
            var files = $('#photo').prop('files');
            console.log(files);

            var data = new FormData();
            data.append('photo', files[0]);

            $.ajax({
                url: '/upload/',
                type: 'POST',
                data: data,
                cache: false,
                processData: false,
                contentType: false
            });
        });
    })

</script>

upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
    <script>
        var csrftoken = $('meta[name=csrf-token]').attr('content')

        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken)
                }
            }
        })
    </script>
</head>
<body>
<form action="{{url_for("upload")}}" method="POST" enctype="multipart/form-data">
    <input type="file" id="photo" name="photo">
    <button type="button">保存</button>
</form>
<script>
    $(function () {
        $('button').click(function () {
            var files = $('#photo').prop('files');
            console.log(files);

            var data = new FormData();
            data.append('photo', files[0]);

            $.ajax({
                url: '/upload/',
                type: 'POST',
                data: data,
                cache: false,
                processData: false,
                contentType: false
            });
        });
    })

</script>
</body>
</html>

创建表单类

class PhotoForm(Form):
    photo = FileField('Your photo')

视图函数

@app.route('/upload/', methods=('GET', 'POST'))
@app.route('/', methods=('GET', 'POST'))
def upload():
    if request.method == "GET":
        return render_template('upload.html')
    else:
        form = PhotoForm(request.files)
        if form.validate():
            basepath = os.path.dirname(__file__)  # 当前文件所在路径
            # 注意:没有的文件夹一定要先创建
            upload_path = os.path.join(basepath, r'.\uploads', secure_filename(form.photo.data.filename))
            form.photo.data.save(upload_path)
            return "{}".format(form.photo.data.filename)
        else:
            filename = None
            return "{}".format(filename)

获取存储位置

考虑安全性,在保存文件前,先使用"werkzeug.utils.secure_filename"来对上传的文件名来进行过滤。

basepath = os.path.dirname(__file__)  # 当前文件所在路径
            # 注意:没有的文件夹一定要先创建
            upload_path = os.path.join(basepath, r'.\uploads', secure_filename(form.photo.data.filename))

获取到上传文件后,使用form.photo.data.save(upload_path)来保存文件

注意上传文件与传递参数的细节

上传文件时使用request.files,传参数时使用request.form

验证上传的文件

  1. 定义表单的时候,对文件的字段,需要采用"FileField"这个类型

  2. 验证器应该从flask_wtf.file中导入,使用flask_wtf.file.FileRequired是用来验证文件上传是否为空,flask_wtf.file.FileAllowed用来验证上传的文件的后缀名

from wtforms import Form,FileField,StringField
from wtforms.validators import  InputRequired
from flask_wtf.file import FileAllowed,FileRequired

class UploadFrom(Form):

# 使用Flask - WTF提供的FileRequired、FileAllowed验证函数

    avatar = FileField(validators=    [FileRequired(),FileAllowed(['jpg','png','gif'])])
    desc = StringField(validators=[InputRequired()])
  1. 在视图文件中,想要将表单数据都传递过来(其中有传递的文件),就使用from werkzeug.datastructures.CombinedMultiDict,来把request.form与request.files来进行合并,再传给表单来做验证

如下:

form = UploadFrom(CombinedMultiDict([request.form,request.files]))

验证码

Flask-WTF 通过 RecaptchaField 也提供对验证码的支持:

from flask_wtf import Form, RecaptchaField
from wtforms import TextField

class SignupForm(Form):
    username = TextField('Username')
    recaptcha = RecaptchaField()

这伴随着诸多配置,你需要逐一地配置它们。

RECAPTCHA_PUBLIC_KEY

必须 公钥

RECAPTCHA_PRIVATE_KEY

必须 私钥

RECAPTCHA_API_SERVER

可选 验证码 API 服务器

RECAPTCHA_PARAMETERS

可选 一个 JavaScript(api.js)参数的字典

RECAPTCHA_DATA_ATTRS

可选 一个数据属性项列表 https://developers.google.com/recaptcha/docs/display

RECAPTCHA_PARAMETERS 和 RECAPTCHA_DATA_ATTRS 的示例:

RECAPTCHA_PARAMETERS = {'hl': 'zh', 'render': 'explicit'}
RECAPTCHA_DATA_ATTRS = {'theme': 'dark'}

对于应用测试时,如果 app.testing 为 True ,考虑到方便测试,Recaptcha 字段总是有效的。

CSRF 保护

为什么需要 CSRF?

Flask-WTF 表单保护免受 CSRF 威胁,不需要有任何担心。尽管如此,如果有不包含表单的视图,那么它们仍需要保护。

例如,由 AJAX 发送的 POST 请求,然而它背后并没有表单。在 Flask-WTF 0.9.0 以前的版本无法获得 CSRF 令牌。这是为什么要实现 CSRF。

SCRECT_KEY随机生成

import os
import base64

secret_key = os.urandom(44)

实现

为了能够让所有的视图函数受到 CSRF 保护,需要开启 CsrfProtec模块:

from flask_wtf.csrf import CsrfProtect
CsrfProtect(app)

像任何其它的 Flask 扩展一样,可以惰性加载它:

from flask_wtf.csrf import CsrfProtect

csrf = CsrfProtect()

app = Flask(__name__)
csrf.init_app(app)

需要为 CSRF 保护设置一个秘钥。通常下,同 Flask 应用的 SECRET_KEY 是一样的。

如果模板中存在表单,你不需要做任何事情。与之前一样:

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

但是如果模板中没有表单,你仍然需要一个 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>

那么后端可用 request.form['csrf_token']可以获取crsf_token值。

无论何时未通过 CSRF 验证,都会返回 400 响应。可以自定义这个错误响应:

@csrf.error_handler
def csrf_error(reason):
    return render_template('csrf_error.html', reason=reason), 400

强烈建议对所有视图启用 CSRF 保护。但也提供了某些视图函数不需要保护的装饰器:

@csrf.exempt
@app.route('/foo', methods=('GET', 'POST'))
def my_handler():
    # ...
    return 'ok'

默认情况下可以在所有的视图中禁用 CSRF 保护,通过设置 WTF_CSRF_CHECK_DEFAULT 为 False,仅仅当需要的时候选择调用 csrf.protect()。这也能够让在检查 CSRF 令牌前做一些预先处理:

@app.before_request
def check_csrf():
    if not is_oauth(request):
        csrf.protect()

AJAX

不需要表单,通过 AJAX 发送 POST 请求成为可能。0.9.0 版本后这个功能变成可用的。

假设已经使用了 CsrfProtect(app),可以通过 {{ csrf_token() }} 获取 CSRF 令牌。这个方法在每个模板中都可以使用,并不需要担心在没有表单时如何渲染 CSRF 令牌字段。

推荐的方式是在 <meta> 标签中渲染 CSRF 令牌:

<meta name="csrf-token" content="{{ csrf_token() }}">

在 <script> 标签中渲染同样可行:

<script type="text/javascript">
    var csrftoken = "{{ csrf_token() }}"
</script>

下面的例子采用了在 <meta> 标签渲染的方式, 在 <script> 中渲染会更简单,无须担心没有相应的例子。

无论何时发送 AJAX POST 请求,为其添加 X-CSRFToken 头:

var csrftoken = $('meta[name=csrf-token]').attr('content')

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken)
        }
    }
})

那么后端可用 request.headers['X-CSRFToken']可以获取crsf_token值。

实例:

app.py

from flask import Flask,render_template,request
from flask_wtf import Form
from wtforms import StringField
from wtforms.validators import DataRequired,Length,EqualTo
from flask_wtf.csrf import CsrfProtect

app = Flask(__name__)
app.config["SECRET_KEY"] = "12345678"

crsf = CsrfProtect()
crsf.init_app(app)

....

if __name__ == '__main__':
    app.run()

login.html

<!DOCTYPE html>
<html lang="en">
<head>
...
    <meta name="csrf-token" content="{{ csrf_token() }}">
...
    <script>
        var csrftoken = $('meta[name=csrf-token]').attr('content')

        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken)
                }
            }
        })
    </script>
</head>
<body>
...
<script>
    $(function () {
        $("#submit").click(function (event) {
            {# 阻止默认事件发生#}
            event.preventDefault()
            var csrf_token = $("meta[name=csrf-token]").attr("content");
            var name = $("form>input[name='name']").val()
            var password1 = $("input[name='password1']").val()
            var password2 = $("input[name='password2']").val()
            $.ajax({
                type: 'POST',
                url: "/login/",
                headers:{"X-CSRFToken":csrf_token},
                data: {
                    "name": name,
                    "password1": password1,
                    "password2": password2,
                },
                success: function (data) {
                    console.log("post 请求成功")
                },
            });
        })
    })
</script>
</body>
</html>
上一页05.flask-sqlalchemy下一页07.session与cookie

最后更新于3年前

这有帮助吗?

image-20200905000329099
image-20200905000346166
image-20200905002211082