在Web应用安全体系里,HTTP响应头常常是最容易被忽略却又成本最低的防护层。Scorched框架本身核心非常精简,很多安全能力需要依赖插件扩展,其中Scorched::Plugins::Security就是一个专门用来补齐安全响应头和跨站请求伪造(CSRF)防护的插件。它并不引入复杂的认证体系,而是通过合理的默认配置和少量代码,在请求进入控制器之前就完成拦截和响应头注入,让应用默认处于更安全的状态。

这个插件的设计思路是:对于每一个经过Scorched路由的响应,自动附加一组经过实践检验的安全头;对于所有非安全方法(例如POST、PUT、DELETE)的请求,强制校验CSRF token。接下来会分别从安全头的作用、CSRF校验机制以及实际配置三个方面展开,确保开发者不仅会套用配置,更能理解每一条策略背后的风险模型。
安全响应头的作用与Scorched中的默认策略
安全响应头通过浏览器识别特定HTTP头来决定是否限制页面行为。Scorched::Plugins::Security默认会注入以下几类关键头:X-Frame-Options、X-Content-Type-Options、X-XSS-Protection,以及可选的Content-Security-Policy。X-Frame-Options用来禁止页面被嵌入到其他站点的<iframe>中,设置成DENY可以完全阻止任何框架嵌套,而SAMEORIGIN则允许同源页面嵌套,适合有内嵌页面需求的场景。许多中小型应用只关注登录和支付环节,却忽略了点击劫持这种几乎零成本的攻击方式,通过这个头可以快速封堵。
X-Content-Type-Options通常设为nosniff,它告诉浏览器不要根据内容猜测MIME类型。比如攻击者上传一个带脚本内容的文件但声明为图片类型,旧版浏览器可能会嗅探并执行其中的脚本。加上这个头后,浏览器会严格按照响应头中的Content-Type处理,降低存储型XSS的风险。至于Content-Security-Policy,它比前几个头复杂得多,Scorched插件允许通过配置项传入策略字符串,比如default-src 'self'来限制资源加载来源,但误配也可能导致静态资源全部被拦截。因此插件默认只开启前几个低风险头,CSP留作可选增强。
在Scorched中配置这些头并不需要手动写入Rack中间件,插件已经处理好了注入逻辑。一个常见误区是:开发者自己在控制器里又重复设置X-Frame-Options,导致响应中出现两个值甚至是冲突的值。Scorched::Plugins::Security会在响应完成后统一处理,所以除非有特殊需求,不要在控制器里重复设置同名头。如果需要覆盖默认策略,应该通过插件提供的配置接口,而不是在响应对象上手动追加。
CSRF token的生成与校验机制
跨站请求伪造利用的是浏览器自动携带同源Cookie的特性。攻击者构造一个恶意页面,里面有一个自动提交的表单,目标是用户已经登录的站点。如果目标站点只依赖Cookie识别身份,请求就会在用户不知情的情况下执行。Scorched::Plugins::Security的CSRF防护采用同步令牌模式:服务端生成一个随机token,存入会话,同时下发到表单隐藏字段或JavaScript可读的变量中。每次非安全方法请求到达时,插件从请求参数或自定义头中取出token,与会话中的值进行安全比较,不一致则直接返回403。
token生成的关键在于随机性和会话绑定。插件内部使用SecureRandom生成足够长的随机字符串,并将其存入Rack session中。默认情况下,token通过表单隐藏字段<input type="hidden" name="_csrf" value="...">提交,也支持从请求头X-CSRF-Token读取,这对AJAX请求非常重要。比较时插件使用恒定时间比较函数,避免时序攻击。需要注意的是,token一旦生成,在整个会话期间通常保持不变,这样可以减轻服务端存储压力,但如果会话被劫持,token也会随之泄露,因此安全等级较高的系统可以选择每次登录后重新生成token。
在Scorched应用中获取和渲染token非常直观。控制器里可以通过插件提供的方法拿到token字符串,然后在视图模板中填入表单。下面给出一个简单路由和视图渲染的示例:
require 'scorched'
require 'scorched/plugins/security'
class App < Scorched::Controller
plugin :security, csrf: true
get '/' do
# render 模板中需要 csrf_token 变量
render :form, csrf_token: session[:csrf]
end
post '/submit' do
# 如果请求中没有有效 token,此处不会执行
"提交成功"
end
end
这段代码里,plugin :security, csrf: true打开了CSRF校验。GET请求返回表单页面,其中隐藏字段需要填入从会话中读取的token。POST /submit 只有在token校验通过后才会执行,否则插件会提前终止请求。这里刻意使用session[:csrf]作为token来源,但实际插件可能封装了专门的方法,开发者应参考当前版本的API,核心逻辑不变。
对于纯JSON API服务,没有表单页面,CSRF防护的策略需要调整。如果API只接受Authorization头而非Cookie,那么CSRF攻击无法利用浏览器自动携带Cookie的特性,此时可以关闭CSRF校验。但如果API同时接受Cookie和自定义头,开启校验是正确的。Scorched::Plugins::Security允许通过配置声明哪些路径跳过CSRF检查,例如使用正则匹配/api/v1/,但要谨慎,不要把所有POST都放行。
实战配置:AJAX请求与安全头冲突处理
现代前端应用中,表单提交大量转变为AJAX调用。CSRF token需要通过JavaScript读取并附加到请求头中。常见做法是在页面中输出一个<meta name="csrf-token" content="...">,然后Ajax库在每次请求前读取该meta并设置X-CSRF-Token头。Scorched::Plugins::Security默认支持从该头读取token,因此不需要额外配置。关键是确保meta标签中的token与会话中的值一致,并且页面本身不要被缓存到跨站CDN中导致token泄露。
一个容易被忽视的问题是:当应用同时配置了严格的安全头时,AJAX跨域请求会先触发CORS预检。如果安全头中设置了Access-Control-Allow-Origin,需要与其配合。Scorched::Plugins::Security默认不处理CORS,如果开发者自行添加了允许跨域的响应头,必须保证X-CSRF-Token被列入允许的请求头列表中,否则浏览器会拦截预检请求,前端无法发送带token的真实请求。这里的冲突不是插件造成的,而是安全策略叠加时常见的配置遗漏。
下面展示一个完整的单文件Scorched应用,包含安全头配置、CSRF token输出、AJAX请求处理,以及JSON API的放行规则:
require 'scorched'
require 'scorched/plugins/security'
require 'json'
class SecureApp < Scorched::Controller
plugin :security,
csrf: true,
headers: {
'X-Frame-Options' => 'DENY',
'X-Content-Type-Options' => 'nosniff',
'Content-Security-Policy' => "default-src 'self'"
},
csrf_exempt: [%r{^/api/}]
get '/' do
token = request.session[:csrf_token]
<<-HTML
<!DOCTYPE html>
<html>
<head><meta name="csrf-token" content="#{token}"></head>
<body>
<form id="f"><input type="hidden" name="_csrf" value="#{token}"></form>
<script>
fetch('/submit', {
method: 'POST',
headers: {'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content}
});
</script>
</body>
</html>
HTML
end
post '/submit' do
'CSRF校验通过'
end
post '/api/v1/update', exempt: true do
# 这里不需要CSRF token,但最好有其他认证方式
content_type 'application/json'
{ok: true}.to_json
end
end
这段代码中的内嵌HTML在源码层面已经做了转义,实际渲染时浏览器会识别为正常标签。注意'X-CSRF-Token'头由前端通过fetch发送,而表单中依然保留了_csrf隐藏字段,以兼容非JavaScript场景。对于/api/v1/update这条路由,代码里通过exempt: true跳过CSRF校验,但在真实项目中API往往需要额外的Token认证或同源策略限制。
最后需要强调的是,安全头与CSRF防护只是Web安全基线的一部分,它们不能替代输入验证、输出编码和权限控制。但正因为实现成本低、对现有代码侵入小,Scorched::Plugins::Security非常适合作为轻量应用的第一步加固措施。在部署到生产环境前,建议使用浏览器开发者工具逐一检查响应头是否符合预期,同时用curl模拟跨站请求验证CSRF拦截效果,确保配置真正生效而不是停留在代码层面。
Scorched插件CSRF防护安全响应头修改时间:2026-09-27 16:25:55