bigywater.tech
Flask · 73

Flask-WTF 表单校验

Flask-WTF 的校验到底在哪一步报什么错?CSRF token 里装了什么、什么情况下会失败?AJAX 的 X-CSRFToken 请求头为什么有时候没用?这篇用 test_client 把每一种错误信息逐个打印出来。

4 种
CSRF 失败的报错:missing / do not match / invalid / expired
3 段
csrf_token 的结构,第一段是 session 里的随机串
0 条
GET 请求时 validate_on_submit() 的错误数

Email() 的依赖

实测 · 本机执行输出
  email_validator 已安装

上一篇:Flask-Login 登录流程;下一篇:Flask-Migrate 基础。

csrf_token 的结构

实测 · 本机执行输出
  渲染结果: <input id="csrf_token" name="csrf_token" type="hidden" value="Ijg4MzJkNWUwYmQ0ZWU0ZjIxYzZjYTAzYmMyYmZmOTBmZjNkMTk4OTYi.ar8q8g.sAeB3FS6Q61qwTk9l22abpUx…
  csrf_token = Ijg4MzJkNWUwYmQ0ZWU0ZjIxYzZjYTAzYmMyYmZmOTBmZjNkMTk4OTYi.ar8q8g.sAeB3FS6Q61qwTk9l22abpUxbLY
  结构: 3 段 | 第 1 段(base64)解码 → b'"8832d5e0bd4ee4f21c6ca03bc2bff90ff3d19896"' (签名过的是 session 里的随机串,不是用户信息)
  session 里多了键: ['csrf_token']

各字段的错误信息

实测 · 本机执行输出
  合法数据 + token                 → 200 {'age': 30, 'agree': True, 'email': 'a@b.co', 'username': 'annie'}
  合法数据,没有 token                → 400 {'csrf_token': ['The CSRF token is missing.']}
  用户名太短                        → 400 {'username': ['长度 3-10']}
  用户名是 admin(自定义校验)            → 400 {'username': ['用户名已被占用(自定义校验)']}
  邮箱格式不对                       → 400 {'email': ['邮箱格式不对']}
  年龄 17                        → 400 {'age': ['18-120']}
  年龄不是数字                       → 400 {'age': ['Not a valid integer value.', '18-120']}
  两次密码不一致                      → 400 {'confirm': ['两次密码不一致']}
  不勾选同意                        → 400 {'agree': ['必须同意']}
  什么都不填                        → 400 {'age': ['18-120'], 'agree': ['必须同意'], 'email': ['This field is required.'], 'password': ['This field is required.'], 'username': ['必填']}

validate_on_submit

实测 · 本机执行输出
  GET /signup → 200 {'errors': {}, 'ok': False, 'submitted': False}

CSRF 的四种失败

实测 · 本机执行输出
  正常: 200
  用别的会话的 token → 400 {'csrf_token': ['The CSRF tokens do not match.']}
  篡改 token 末尾 → 400 {'csrf_token': ['The CSRF token is invalid.']}
  WTF_CSRF_TIME_LIMIT=1 秒,等 2.5 秒再提交(时间戳精度是秒,要等够 2 秒才保证 age > 1) → 400 {'csrf_token': ['The CSRF token has expired.']}
  WTF_CSRF_ENABLED=False(测试时常用)→ 200

请求头与 JSON 请求体

实测 · 本机执行输出
  表单数据 + 头 X-CSRFToken → 400 {'csrf_token': ['The CSRF token is missing.']}
  JSON 请求体 + 头 X-CSRFToken → 400 {'csrf_token': ['The CSRF token is missing.']}

只有 FlaskForm 不认请求头;用了 CSRFProtect 就可以(见下一节)。

CSRFProtect

实测 · 本机执行输出
  POST /p 没有 token → 400 <!doctype html> <html lang=en> <title>400 Bad Request</title> <h1>Bad Request</h1> <p>The CSRF token is missin
  带 token 的 POST → 200

文件上传校验

实测 · 本机执行输出
  没有文件 → {'f': ['This field is required.']}
  a.exe → {'f': ['只允许 png/jpg']}
  a.png → {'errors': {}, 'ok': True}

分步演示

Flask-WTF 表单验证
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Flask-WTF 1.3.0、WTForms 3.2.2、email_validator
    • 没有验证:真实浏览器里 CSRF 攻击的复现、SameSite cookie 对 CSRF 的影响(见第 125 篇)、FieldList/FormField 嵌套表单、Flask-WTF 的 recaptcha、文件上传的大小限制(见相关篇)
    京ICP备2026067502号-1京公网安备11010502063487号