web-dev-qa-db-fra.com

Pourquoi est-ce que j'obtiens une boucle de redirection infinie avec force_ssl dans mon Rails app?

Je veux que mon contrôleur API utilise SSL, j'ai donc ajouté une autre directive d'écoute à mon 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;
}

qui passe le conftest nginx sans aucun problème. J'ai également ajouté un force_ssl directive à mon 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

qui fonctionnait très bien quand je n'utilisais pas SSL, mais maintenant quand j'essaie de curl --LI http://foo/api/auth.json, Je suis correctement redirigé vers https, mais je continue à être redirigé vers http://foo/api/auth se terminant par une boucle de redirection infinie.

Mes itinéraires ont simplement

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

J'utilise Rails 3.2.1 sur Ruby 1.9.2 avec nginx 0.7.65

64
Jakub Arnold

Vous ne transmettez aucune information indiquant si cette demande était une demande terminée par HTTPS ou non. Normalement, dans un serveur, le "ssl on;" La directive définira ces en-têtes, mais vous utilisez un bloc combiné.

Rack (et force_ssl) détermine SSL par:

  • Si la demande est arrivée sur le port 443 (cela n'est probablement pas renvoyé à Unicorn depuis nginx)
  • Si ENV ['HTTPS'] == "on"
  • Si l'en-tête X-Forwarded-Proto == "HTTPS"

Voir la source force_ssl pour l'histoire complète.

Puisque vous utilisez un bloc combiné, vous souhaitez utiliser le troisième formulaire. Essayer:

proxy_set_header X-Forwarded-Proto $scheme;

dans votre serveur ou bloc d'emplacement selon la documentation nginx .

Cela mettra l'en-tête sur "http" lorsque vous entrerez sur une demande de port 80 et le mettra sur "https" lorsque vous entrerez sur une demande 443.

124
Chris Heald

Essayez de définir cette directive dans votre nginx location @Unicorn bloquer:

proxy_set_header X-Forwarded-Proto https;

J'ai eu ce même problème et j'ai enquêté sur le gestionnaire de middleware Rack (pas force_ssl mais similaire) Je pouvais voir qu'il s'attendait à ce que l'en-tête soit défini pour déterminer si la demande était déjà traitée comme SSL par nginx.

21
Cody Caughlan