web-dev-qa-db-ja.com

Railsアプリでforce_sslを使用すると無限リダイレクトループが発生するのはなぜですか?

APIコントローラーにSSLを使用させたいので、別のlistenディレクティブをnginx.confに追加しました

upstream Unicorn {
  server unix:/tmp/Unicorn.foo.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  listen 443 ssl default;
  ssl_certificate /etc/ssl/certs/foo.crt;
  ssl_certificate_key /etc/ssl/private/foo.key;

  server_name foo;
  root /var/apps/foo/current/public;

  try_files $uri/system/maintenance.html $uri/index.html $uri @Unicorn;

  location @Unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_Host;
    proxy_redirect off;
    proxy_pass http://Unicorn;
  }

  error_page 502 503 /maintenance.html;
  error_page 500 504 /500.html;
  keepalive_timeout 5;
}

これは、nginx conftestに問題なく合格します。また、ApiControllerにforce_sslディレクティブを追加しました

class ApiController < ApplicationController
  force_ssl if Rails.env.production?

  def auth
    user = User.authenticate(params[:username], params[:password])
    respond_to do |format|
      format.json do
        if user
          user.generate_api_key! unless user.api_key.present?
          render json: { key: user.api_key }
        else
          render json: { error: 401 }, status: 401
        end
      end
    end
  end

  def check
    user = User.find_by_api_key(params[:api_key])
    respond_to do |format|
      format.json do
        if user
          render json: { status: 'ok' }
        else
          render json: { status: 'failure' }, status: 401
        end
      end
    end
  end
end

sSLを使用していないときはうまく機能しましたが、curl --LI http://foo/api/auth.jsonにしようとすると、httpsに適切にリダイレクトされますが、http://foo/api/authにリダイレクトされ続けます無限リダイレクトループで。

私のルートは単純に

get "api/auth"
get "api/check"

Rails 3.2.1 on Ruby 1.9.2 with nginx 0.7.65を使用しています。

64
Jakub Arnold

このリクエストがHTTPSで終了したリクエストであるかどうかに関する情報を転送していません。通常、サーバーでは、「ssl on;」ディレクティブはこれらのヘッダーを設定しますが、結合ブロックを使用しています。

ラック(およびforce_ssl)は、以下によってSSLを決定します。

  • リクエストがポート443で受信された場合(これはnginxからUnicornに返されない可能性が高い)
  • ENV ['HTTPS'] == "on"の場合
  • X-Forwarded-Protoヘッダー== "HTTPS"の場合

詳細については、 force_sslソース を参照してください。

結合ブロックを使用しているため、3番目の形式を使用する必要があります。試してください:

proxy_set_header X-Forwarded-Proto $scheme;

サーバーまたはロケーションブロックで nginxのドキュメントによる

これにより、ポート80の要求を受信するとヘッダーが「http」に設定され、443の要求を受信するとヘッダーが「https」に設定されます。

124
Chris Heald

Nginx location @Unicornブロックでこのディレクティブを設定してみてください:

proxy_set_header X-Forwarded-Proto https;

この同じ問題があり、Rackミドルウェアハンドラー(force_sslではなく類似)を調査したところ、リクエストが既にnginxによってSSLとして処理されているかどうかを判断するためにヘッダーが設定されることを期待していることがわかりました。

21
Cody Caughlan