基于域名在Rails中条件性实现HSTS、SSL及安全Cookie
嘿,这个场景我之前帮不少Rails开发者处理过,在单应用多域名的情况下,既要给部分域名上SSL、配置HSTS和安全Cookie,又要保留其他域名的HTTP访问,确实不能用全局配置一刀切。Rails 4.x里有几个比只靠ApplicationController更优的方案,给你梳理清楚:
中间件运行在Rails处理请求的早期和响应的晚期,正好能精准控制HSTS头和Cookie的安全属性,而且不会影响全局配置。
步骤1:创建中间件文件
在app/middlewares/目录下新建domain_ssl_middleware.rb(如果没有middlewares文件夹就自己建一个):
class DomainSslMiddleware # 把需要启用SSL的域名放到这个数组里 SSL_DOMAINS = %w(your-ssl-domain.com another-ssl-domain.net) def initialize(app) @app = app end def call(env) status, headers, response = @app.call(env) request = Rack::Request.new(env) if SSL_DOMAINS.include?(request.host) # 给SSL域名的HTTPS响应添加HSTS头,max-age可以根据需求调整,includeSubDomains可选 headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' if status == 200 && request.ssl? # 自动给所有Cookie加上Secure标记,确保只有HTTPS环境下才会发送 headers['Set-Cookie']&.gsub!(/([^;]+)/, '\1; Secure') if headers['Set-Cookie'] else # 非SSL域名要确保不发送HSTS头,并且移除Cookie的Secure标记(避免浏览器拒绝保存) headers.delete('Strict-Transport-Security') headers['Set-Cookie']&.gsub!(/; Secure/, '') if headers['Set-Cookie'] end [status, headers, response] end end
步骤2:注册中间件
在config/application.rb里添加一行,把中间件加入Rails的中间件栈:
config.middleware.use DomainSslMiddleware
这个方案的好处是:
- 早于控制器处理请求,能完美适配HSTS的浏览器要求(必须在HTTPS响应中返回)
- 精准针对域名控制Cookie的Secure属性,不会搞混SSL和非SSL域名的Cookie
- 完全避开全局配置的一刀切问题
如果你的应用用Nginx或Apache做反向代理,直接在服务器层面分域名配置会更高效,毕竟Web服务器处理请求的性能比Rails好很多。
以Nginx为例:
针对SSL域名的server块
server { listen 443 ssl; server_name your-ssl-domain.com another-ssl-domain.net; # 这里放你的SSL证书配置、反向代理配置... # 添加HSTS头,always确保即使是跳转响应也会带上 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # 把请求协议传递给Rails,让Rails知道当前是HTTPS proxy_set_header X-Forwarded-Proto $scheme; }
针对非SSL域名的server块
server { listen 80; server_name http-only-domain.org blog.http-domain.com; # 这里放你的反向代理配置... # 不需要添加HSTS头,保持默认即可 }
Rails端配合配置
在config/application.rb里关掉全局强制SSL,并信任反向代理的协议标识:
config.force_ssl = false # 必须关掉,不然会强制所有域名走HTTPS config.action_dispatch.trusted_proxies = ['127.0.0.1'] # 替换成你的代理服务器IP
然后在config/initializers/session_store.rb里,让Cookie的Secure属性动态判断请求协议:
Rails.application.config.session_store :cookie_store, key: '_your_app_session', secure: ->(request) { request.ssl? }, # 只有HTTPS请求时才加Secure标记 httponly: true
这个方案的优势:
- Web服务器处理HSTS和协议转发更高效,减轻Rails的负担
- 分域名配置更直观,每个server块对应一套策略
- 动态Cookie属性配置精准适配不同域名的协议
如果需要对特定动作做跳转(比如把SSL域名的HTTP请求强制跳转到HTTPS),可以在ApplicationController里加个前置动作,作为前两个方案的补充:
class ApplicationController < ActionController::Base SSL_DOMAINS = %w(your-ssl-domain.com another-ssl-domain.net) before_action :enforce_protocol_by_domain private def enforce_protocol_by_domain if SSL_DOMAINS.include?(request.host) && !request.ssl? # SSL域名的HTTP请求跳转到HTTPS,永久重定向 redirect_to protocol: 'https://', status: :moved_permanently elsif !SSL_DOMAINS.include?(request.host) && request.ssl? # 非SSL域名的HTTPS请求跳转到HTTP(可选,根据你的需求决定) redirect_to protocol: 'http://', status: :moved_permanently end end end
注意:单独用控制器跳转没法设置HSTS头,因为第一次跳转是HTTP 301响应,不会带HSTS,所以一定要配合中间件或服务器配置。
几个重要提醒
- Rails 4.x的
config.force_ssl是全局生效的,一定要关掉,不然会强制所有域名走HTTPS - HSTS头的
max-age设置后浏览器会缓存很久,测试阶段可以用max-age=0来快速清除缓存 - 非SSL域名的Cookie绝对不能加Secure标记,不然浏览器会拒绝保存这些Cookie
内容的提问来源于stack exchange,提问作者Jason Logsdon

