仅允许消息总线进行跨源请求

我正在尝试实现一个可以嵌入任何网站的聊天小部件,为此我决定使用 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 个赞

您可以为每个响应添加额外的响应头,请参阅文档

例如,在 config/initializers/message_bus.rb 中:

MessageBus.extra_response_headers_lookup do |env|
  [
    ["Access-Control-Allow-Origin", "http://example.com:3000"],
  ]
end
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

我将尝试用这种方式代替自定义中间件,并更新此帖子。

我也没能成功。目前我保留了自定义中间件。