메시지 버스에 대해서만 교차 출처 요청 허용

어떤 웹사이트에나 임베드할 수 있는 채팅 위젼을 구현하려고 하고 있으며, 이를 위해 위젼과 Rails 백엔드 간의 통신에 MessageBus를 사용하기로 결정했습니다. 어떤 오리진에서든 임베드될 수 있으므로 크로스 오리진 요청을 처리해야 했습니다.

하지만 저는 메시지 버스 요청에 대해서만 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)
    # 메시지 버스 요청이고 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

하지만 이 미들웨어를 사용해도 여전히 크로스 오리진 문제가 발생합니다.

다른 아이디어가 있을까요?

또 하나 더 좋은 점은 특정 채널에 대해서만 크로스 오리진을 활성화하는 것입니다. (제 경우 채팅 메시지 버스 채널입니다.) 저는 크로스 오리진 요청을 하지 않는 애플리케이션의 다른 부분들에서도 MessageBus를 사용하고 있으며, 이 상태 그대로 유지하고 싶습니다.

두 번째 방법(메시지 버스 요청에 대해서만 CORS를 활성화하는 사용자 정의 미들웨어)을 사용하여 문제를 해결했습니다. 문제는 access-control-allow-headers가 올바르게 설정되지 않았기 때문이었습니다. 해결 방법은 응답의 access-control-allow-headers에 요청 헤더인 Access-Control-Request-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)
    # 메시지 버스이고 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"는 Cross-Origin Resource Sharing(CORS) 요청의 맥락에서 사용됩니다. CORS는 웹 애플리케이션이 애플리케이션이 생성된 도메인 이외의 다른 도메인에 요청을 보낼 수 있게 하는 메커니즘입니다.

웹 애플리케이션이 크로스 오리진 요청을 수행할 때, 실제 요청이 허용되는지 확인하기 위해 서버에 초기 “프리플라이트(preflight)” 요청을 보냅니다. 이 프리플라이트 요청에는 실제 요청에 포함하려는 추가 헤더 목록이 담긴 “Access-Control-Request-Headers” 헤더가 포함됩니다.

예를 들어, 웹 애플리케이션이 크로스 오리진 요청에 "X-Auth-Token"이나 "Authorization"과 같은 사용자 정의 헤더를 포함하려면, 프리플라이트 요청의 “Access-Control-Request-Headers” 헤더에 해당 헤더들을 명시해야 합니다.

프리플라이트 요청을 받는 서버는 “Access-Control-Request-Headers” 헤더를 검사하여 요청된 헤더가 실제 요청에 허용되는지 판단할 수 있습니다. 서버가 요청된 헤더를 승인하면, 프리플라이트 응답에 적절한 Access-Control-Allow-Headers 헤더를 포함하여 응답함으로써 웹 애플리케이션이 실제 요청을 진행할 수 있도록 합니다.

요약하자면, "Access-Control-Request-Headers"는 웹 애플리케이션이 크로스 오리진 요청에 포함하려는 추가 헤더를 지정하기 위해 CORS 프리플라이트 요청에서 사용되는 HTTP 헤더입니다.

다른 분들도 같은 문제를 해결하고 계실 수 있어 공유합니다. 특히 이 구현 방식이 야기할 수 있는 보안 문제와 관련하여 피드백을 환영합니다.

문서에 따라 응답 헤더를 추가로 추가할 수 있습니다:

예: config/initializers/message_bus.rb 파일에서:

MessageBus.extra_response_headers_lookup do |env|
 [
    ["Access-Control-Allow-Origin", "http://example.com:3000"],
  ]
end

잊어버리고 말했지만, 이것이 제가 처음 시도한 방법인데 작동하지 않았습니다. 하지만 그것은 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

커스텀 미들웨어 대신 이 방식으로 다시 시도해 보고 이 스레드를 업데이트하겠습니다.

그것도 안 됐어요. 일단 커스텀 미들웨어를 유지하고 있습니다.