Flask-JWT-Extended集成Flask-Admin时,HTML表单双重CSRF令牌使用问题
我刚好处理过几乎一模一样的场景,给你几个亲测可行的解决方案,按推荐程度排序:
方案1:重载Flask-Admin模板,用JS劫持表单提交(最推荐)
Flask-Admin的原生表单是普通POST提交,不会自动携带X-CSRF-TOKEN请求头,所以我们可以通过修改模板,用JavaScript把表单提交改成带CSRF头的Fetch请求,完美适配Flask-JWT-Extended的双重验证规则。
具体步骤:
- 从Flask-Admin的源码中复制你需要修改的模板(比如
model/edit.html、model/create.html,对应编辑和创建表单),放到你项目的templates/admin/model/目录下(保持和原模板一致的路径结构,Flask会优先加载你本地的模板)。 - 在模板的
{% 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一样的效果:
- 在你的项目
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 %}
- 在你的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.
相关产品推荐
相关产品推荐

