Почему я получаю бесконечный цикл перенаправления с помощью force_ssl в моем приложении Rails? - PullRequest
63 голосов
/ 26 февраля 2012

Я хочу, чтобы мой контроллер 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. Я также добавил директиву force_ssl в мой ApiController

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 на Ruby 1.9.2 с nginx 0.7.65

Ответы [ 2 ]

123 голосов
/ 26 февраля 2012

Вы не пересылаете информацию о том, был ли этот запрос завершен HTTPS-запросом или нет. Обычно на сервере "ssl on;" Директива установит эти заголовки, но вы используете комбинированный блок.

Rack (и force_ssl) определяет SSL по:

  • Если запрос поступил через порт 443 (вероятно, он не передается обратно в Unicorn из nginx)
  • Если ENV ['HTTPS'] == "on"
  • Если заголовок X-Forwarded-Proto == "HTTPS"

См. источник force_ssl для полной истории.

Поскольку вы используете комбинированный блок, вы хотите использовать третью форму. Попробуйте:

proxy_set_header X-Forwarded-Proto $scheme;

в вашем сервере или блоке местоположения согласно документации nginx .

Это установит заголовок "http", когда вы входите на запрос порта 80, и установите его на "https", когда вы входите на запрос 443.

21 голосов
/ 26 февраля 2012

Попробуйте установить эту директиву в вашем блоке nginx location @unicorn:

proxy_set_header X-Forwarded-Proto https;

У меня была такая же проблема, и я исследовал обработчик промежуточного программного обеспечения Rack (не force_ssl, но похоже)мог видеть, что он ожидал, что заголовок будет установлен, чтобы определить, был ли запрос уже обработан как SSL как nginx.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...