为啥我在 Rails 应用程序中使用 force_ssl 获得无限重定向循环?

Posted

技术标签:

【中文标题】为啥我在 Rails 应用程序中使用 force_ssl 获得无限重定向循环?【英文标题】:Why am I getting infinite redirect loop with force_ssl in my Rails app?为什么我在 Rails 应用程序中使用 force_ssl 获得无限重定向循环? 【发布时间】:2012-03-15 22:21:05 【问题描述】:

我想让我的 API 控制器使用 SSL,所以我在我的 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"

我在 Ruby 1.9.2 和 nginx 0.7.65 上使用 Rails 3.2.1

【问题讨论】:

【参考方案1】:

您不会转发有关此请求是否为 HTTPS 终止请求的任何信息。通常,在服务器中,“ssl on;”指令将设置这些标题,但您使用的是组合块。

Rack(和 force_ssl)通过以下方式确定 SSL:

如果请求来自 443 端口(这可能不会从 nginx 传回 Unicorn) 如果 ENV['HTTPS'] == "on" 如果 X-Forwarded-Proto 标头 == "HTTPS"

请参阅the force_ssl source 了解全文。

由于您使用的是组合块,因此您希望使用第三种形式。试试:

proxy_set_header X-Forwarded-Proto $scheme;

在您的服务器或位置块per the nginx documentation。

这将在您收到端口 80 请求时将标头设置为“http”,并在您收到 443 请求时将其设置为“https”。

【讨论】:

纯魔法。 :) 就我而言,我正在将 Rails 站点转移到使用 Varnish。该站点是纯 HTTPS,并且由于 Varnish 不支持 SSL 并且Passenger 公开了一个套接字而不是一个端口,因此需要两个 Nginx 服务器配置,一个 Varnish 的任一侧。将乘客套接字连接与端口 443 上的 HTTPS 配置分开会导致重定向循环。这解决了它。谢谢! 花了几个小时寻找这个答案。谢谢!【参考方案2】:

尝试在你的 nginx location @unicorn 块中设置这个指令:

proxy_set_header X-Forwarded-Proto https;

我遇到了同样的问题,并调查了 Rack 中间件处理程序(不是force_ssl,但类似),我可以看到它期望设置标头以确定请求是否已被 nginx 处理为 SSL。

【讨论】:

这会将所有请求标记为 SSL 请求,甚至端口 80 请求,因为它是一个组合的服务器块。

以上是关于为啥我在 Rails 应用程序中使用 force_ssl 获得无限重定向循环?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我在 rails 2 中使用 vpim 出现编码错误?

Rails 迁移:删除约束

Rails4:为啥 resque 工人不找工作

为啥“rails s”不能从应用程序目录工作?

rails_3_question :as => 为啥在设置 slug 后我的 /posts/new 路由到 posts/show

可以删除所有用户的所有 Rails 会话信息