我正在尝试实现一个可以嵌入任何网站的聊天小部件,为此我决定使用 MessageBus 在小部件和我的 Rails 后端之间进行通信。由于它可以嵌入到任何源中,因此我需要处理跨源请求。
但是,我只想为 message bus 请求启用 CORS,而不是我所有其他路由。我已经看到了这个问题 CORS configuration · Issue #135 · discourse/message_bus · GitHub
这是我在 config/initializers/cors.rb 中所做的。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options]
end
end
这可以工作,但它允许任何路由的跨源请求,这并不是我想要的。
我也尝试过创建自己的中间件
# app/middleware/message_bus_cors_middleware.rb
class MessageBusCorsMiddleware
def initialize(app)
@app = app
end
def call(env)
# 如果是 message bus 并且是 OPTIONS 请求,则返回 CORS 标头
if env['PATH_INFO'].start_with?('/message-bus') && env['REQUEST_METHOD'] == 'OPTIONS'
# 为 /message-bus 请求应用 CORS 标头
headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD',
'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Visitor-Token',
'Access-Control-Max-Age' => '86400'
}
[200, headers, []]
else
@app.call(env)
end
end
end
但是使用此中间件我仍然遇到跨源问题。
还有其他想法吗?
另外,如果能只为特定频道启用跨源请求(在本例中是聊天 message bus 频道)那就太好了。我还在应用程序的其他部分使用 MessageBus,但它们不需要跨源请求,我想保持这种状态。
2 个赞
我使用第二种方法(自定义中间件仅为 Message Bus 请求启用 cors)成功解决了问题。问题在于 access-control-allow-headers 设置不正确。修复方法是使用 Access-Control-Request-Headers 请求头填充响应的 access-control-allow-headers。
这是最终的实现:
class MessageBusCorsMiddleware
HTTP_ORIGIN = 'HTTP_ORIGIN'
HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'
HTTP_ACCESS_CONTROL_REQUEST_METHOD = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD'
HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'
OPTIONS = 'OPTIONS'
REQUEST_METHOD = 'REQUEST_METHOD'
PATH_INFO = 'PATH_INFO'
def initialize(app)
@app = app
end
def call(env)
# 如果是 message bus 请求且是 OPTIONS 请求,则返回 CORS 头
if env[PATH_INFO].start_with?('/message-bus') && env[REQUEST_METHOD] == OPTIONS && env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
origin = env['HTTP_ORIGIN']
# 为 /message-bus 请求应用 CORS 头
headers = {
"access-control-allow-origin" => "*",
"access-control-allow-methods" => "GET, POST, PUT, PATCH, DELETE, OPTIONS",
"access-control-expose-headers" => "",
"access-control-max-age" => "7200"
}
headers.merge!('access-control-allow-headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]) if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
[200, headers, []]
else
@app.call(env)
end
end
end
我之前不理解 HTTP 头 “Access-Control-Request-Headers” 的作用,所以我问了 ChatGPT:
HTTP 头 “Access-Control-Request-Headers” 在跨域资源共享 (CORS) 请求的上下文中被使用。CORS 是一种允许 Web 应用程序向与应用程序源自的域不同的域发出请求的机制。
当 Web 应用程序发出跨域请求时,它通常会向服务器发送一个初始的“预检”请求,以确定实际请求是否被允许。“预检”请求包含 “Access-Control-Request-Headers” 头,该头列出了应用程序希望在实际请求中包含的其他头。
例如,如果 Web 应用程序希望在其跨域请求中包含自定义头,如 “X-Auth-Token” 或 “Authorization”,它会在“预检”请求的 “Access-Control-Request-Headers” 头中指定这些头。
接收“预检”请求的服务器可以检查 “Access-Control-Request-Headers” 头,以确定所请求的头是否允许用于实际请求。如果服务器批准了所请求的头,它会在“预检”响应中以适当的 Access-Control-Allow-Headers 头进行响应,允许 Web 应用程序继续进行实际请求。
总之,“Access-Control-Request-Headers” 是一个在 CORS 预检请求中使用的 HTTP 头,用于指定 Web 应用程序希望在其跨域请求中包含的其他头。
我分享这些,以防其他人尝试实现相同的功能。我也欢迎反馈,特别是关于此实现可能带来的安全问题。
1 个赞
我忘了说,这是我尝试的第一个方法,但它不起作用。但在我发现 Access-Control-Allow-Headers 之前。也许我像这样添加它们会起作用。
MessageBus.extra_response_headers_lookup do |env|
[
["Access-Control-Allow-Origin", "http://example.com:3000"],
["Access-Control-Allow-Headers", env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]]
]
end
我将尝试用这种方式代替自定义中间件,并更新此帖子。