You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask-JWT-Extended集成Flask-Admin时,HTML表单双重CSRF令牌使用问题

我刚好处理过几乎一模一样的场景,给你几个亲测可行的解决方案,按推荐程度排序:

方案1:重载Flask-Admin模板,用JS劫持表单提交(最推荐)

Flask-Admin的原生表单是普通POST提交,不会自动携带X-CSRF-TOKEN请求头,所以我们可以通过修改模板,用JavaScript把表单提交改成带CSRF头的Fetch请求,完美适配Flask-JWT-Extended的双重验证规则。

具体步骤:

  1. 从Flask-Admin的源码中复制你需要修改的模板(比如model/edit.html、model/create.html,对应编辑和创建表单),放到你项目的templates/admin/model/目录下(保持和原模板一致的路径结构,Flask会优先加载你本地的模板)。
  2. 在模板的{% endblock %}标签之前,添加这段JavaScript代码:
// 从Cookie中读取Flask-JWT-Extended生成的CSRF令牌
function getCsrfToken() {
  const cookieValue = document.cookie
    .split('; ')
    .find(row => row.startsWith('csrf_access_token='))
    ?.split('=')[1];
  return cookieValue || '';
}

// 劫持所有表单的提交事件
document.querySelectorAll('form').forEach(form => {
  form.addEventListener('submit', async function(e) {
    e.preventDefault(); // 阻止默认的表单提交

    const csrfToken = getCsrfToken();
    const formData = new FormData(this);

    try {
      const response = await fetch(this.action, {
        method: this.method,
        headers: {
          'X-CSRF-TOKEN': csrfToken,
          // 保持表单的Content-Type,避免后端解析异常
          'Accept': 'text/html, */*'
        },
        body: formData,
        credentials: 'include' // 确保携带Cookie
      });

      // 处理响应:成功则跳转回原页面或指定页面,失败则提示
      if (response.ok) {
        window.location.href = response.url || window.location.pathname;
      } else {
        alert('表单提交失败,请检查权限或重试');
      }
    } catch (err) {
      console.error('提交出错:', err);
      alert('网络异常,请稍后重试');
    }
  });
});

这段代码会自动把所有Flask-Admin表单的提交改成符合要求的请求,不需要手动修改任何表单逻辑。

方案2:全局注入JS,无需修改单个模板

如果不想复制一堆Flask-Admin的模板文件,可以在你的全局基础模板(或者Flask-Admin的base.html模板)中添加全局JS,实现和方案1一样的效果:

  1. 在你的项目templates/admin/base.html(如果没有就新建,继承Flask-Admin的base模板)中添加:
{% extends 'admin/base.html' %}

{% block tail %}
{{ super() }}
<script>
// 这里放方案1中的JS代码即可
function getCsrfToken() {
  const cookieValue = document.cookie
    .split('; ')
    .find(row => row.startsWith('csrf_access_token='))
    ?.split('=')[1];
  return cookieValue || '';
}

document.querySelectorAll('form').forEach(form => {
  form.addEventListener('submit', async function(e) {
    e.preventDefault();
    const csrfToken = getCsrfToken();
    const formData = new FormData(this);

    try {
      const response = await fetch(this.action, {
        method: this.method,
        headers: { 'X-CSRF-TOKEN': csrfToken },
        body: formData,
        credentials: 'include'
      });

      if (response.ok) {
        window.location.href = response.url || window.location.pathname;
      } else {
        alert('提交失败,请重试');
      }
    } catch (err) {
      console.error(err);
      alert('网络错误');
    }
  });
});
</script>
{% endblock %}
  1. 在你的ModelView中指定使用这个自定义的base模板:
class CustomModelView(ModelView):
    base_template = 'admin/base.html'
    # 你的is_accessible逻辑...
关键配置注意事项

不管用哪个方案,都要确保你的Flask-JWT-Extended配置允许前端读取CSRF Cookie:

app.config['JWT_COOKIE_HTTPONLY'] = False  # 必须设为False,否则JS读不到Cookie里的CSRF令牌
app.config['JWT_CSRF_IN_COOKIES'] = True  # 开启CSRF Cookie(默认就是True)
方案3:修改CSRF验证逻辑(应急用,不推荐)

如果暂时不想改前端,可以临时调整Flask-JWT-Extended的CSRF验证规则,允许从表单字段读取CSRF令牌(但会降低安全性,适合内部后台场景):

from flask_jwt_extended import CSRFProtect

csrf = CSRFProtect(app)

# 自定义CSRF验证函数
@csrf.validate_csrf
def custom_csrf_validation():
    # 优先从Header取,再从表单字段取
    csrf_token = request.headers.get('X-CSRF-TOKEN') or request.form.get('csrf_token')
    if not csrf_token:
        return False
    # 调用原生验证逻辑
    return csrf._validate_csrf(csrf_token)

然后在你的ModelView表单中添加一个隐藏的CSRF字段,填充Cookie中的令牌值即可。


内容的提问来源于stack exchange,提问作者Tomáš M.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:05:46