Rails重定向到https

34

我在维护一个Ruby on Rails网站,我不确定如何使用https协议执行相对URL的重定向。

我可以成功地创建一个使用http的相对URL重定向,例如:

redirect_to "/some_directory/"

但我不知道如何使用HTTPS协议创建重定向到URL。我只能够通过使用绝对URL来实现,例如:

redirect_to "https://mysite.com/some_directory/"

我希望保持我的代码整洁,使用相对URL似乎是个好主意。有人知道如何在Rails中实现这一点吗?


我可以请您澄清一下问题吗?您是想强制让用户在您的网站上始终使用HTTPS,还是只对某些URL进行强制?默认情况下,RAILS将在当前请求为HTTPS时继续使用HTTPS。 - scottd
10个回答

40

ActionController::Base#redirect_to 方法需要一个选项哈希,其中一个参数是 :protocol,它允许你调用:

redirect_to :protocol => 'https://', 
            :controller => 'some_controller', 
            :action => 'index'
参见#redirect_to#url_for的定义,以获取有关选项的更多信息。或者,特别是如果要为所有控制器操作使用SSL,您可以采用更具声明性的方法,使用before_filter。在ApplicationController中,您可以定义以下方法:
def redirect_to_https
    redirect_to :protocol => "https://" unless (request.ssl? || request.local?)
end

您可以在需要 SSL 的操作中的控制器中添加筛选器,例如:

class YourController
    before_filter :redirect_to_https, :only => ["index", "show"]
end

如果你想在整个应用中使用 SSL,可以在 ApplicationController 中声明过滤器:

class ApplicationController
    before_filter :redirect_to_https
end

如果你将一个哈希传递给 redirect_to 方法,那么你可以使用 :protocol => 'https://' 来实现重定向到 HTTPS 协议。但是,如果你要传递一个相对 URL,像上面的问题中那样,这种方法不起作用。总的来说,我同意使用 before_filter 是更好的选择。 - Michael Sepcot
请注意,重定向将删除所有参数。如果您只想为协议重定向,请按以下方式调用: redirect_to({:protocol => "https://"}.merge(request.parameters)) - Chenglu

37

如果您希望您的整个应用程序通过https提供服务,那么自从Rails 4.0以来,最好的方法是在配置文件中启用force_ssl,如下所示:

# config/environments/production.rb
Rails.application.configure do
  # [..]

  # Force all access to the app over SSL, use Strict-Transport-Security,
  # and use secure cookies.
  config.force_ssl = true
end

默认情况下,这个选项已经在新生成的应用程序中的 config/environments/production.rb 中存在,但被注释掉了。

正如注释所说,这不仅会重定向到 https,还会设置Strict-Transport-Security头部(HSTS)并确保所有 Cookie 上设置了安全标志。这两个措施都可以增加应用程序的安全性而几乎没有副作用。它使用了ActionDispatch:SSL

HSTS 过期设置默认为一年,不包括子域名,这对大多数应用程序来说可能是可以接受的。您可以使用hsts选项进行配置:

config.hsts = {
  expires: 1.month.to_i,
  subdomains: false,
}
如果你正在运行Rails 3(>=3.1),或者不想为整个应用程序使用https,则可以在控制器中使用force_ssl方法:
class SecureController < ApplicationController
  force_ssl
end

就这些了。您可以在每个控制器中设置它,或者在您的ApplicationController中设置。您可以使用熟悉的ifunless选项有条件地强制使用https;例如:

# Only when we're not in development or tests
force_ssl unless: -> { Rails.env.in? ['development', 'test'] }

1
如果您在隧道后面进行开发,force_ssl 不是一个很好的选择。例如,ngrok tls 隧道允许您指定证书。然后,ngrok 使用指定的证书终止 tls 连接并将未加密的流量转发到您的应用程序(因此您的应用程序认为一切都是 http)。这意味着您不必在开发环境中设置 nginx 代理并使用 https。 - Michael Johnston
1
@MichaelJohnston 这就是为什么你可以在开发中使用 unless 来不强制使用 SSL(请参见我的答案中的最后一行)。 - Martin Tournoij
@MichaelJohnston 这个应该只在生产环境中开启,不会影响你的本地开发设置。 - Chris Hough
AWS允许您在负载均衡器上终止SSL。然后将未加密的数据转发到EC2实例(其中部署了您的应用程序)。不幸的是,在此处,即使指定了上述“除非”条件,force_ssl也不是一个好选择。 - Christian Fazzini

11

你最好使用ssl_requirement,不用担心链接或重定向是否使用https。通过ssl_requirement,你可以声明哪些操作需要SSL,哪些能够使用SSL,哪些必须不使用SSL。

如果你要重定向到Rails应用程序之外的其他地方,则像Olly建议的那样指定协议即可。


18
现在看看force_ssl,它在Rails 3.1及以上版本中可用。 - Matt Sanders
3
如果你把你的评论单独写成一个答案,我会投票支持它。 - Spundun

3
如果您想全局控制控制器生成的url协议,可以在应用程序控制器中重写url_options方法。您可以根据Rails环境强制生成的url协议,例如:
 def url_options
    super
    @_url_options.dup.tap do |options|
      options[:protocol] = Rails.env.production? ? "https://" : "http://"
      options.freeze
    end
  end

这个示例适用于Rails 3.2.1,我不确定早期或未来的版本是否适用。


3

如果您想强制所有流量通过 https,那么在Rails 6中最好的方法是配置production.rb

config.force_ssl = false

如果您需要更灵活的解决方案,可以使用一个简单的before_action过滤器来处理:

class ApplicationController < ActionController::Base

  include SessionsHelper
  include LandingpageHelper
  include ApplicationHelper
  include UsersHelper
  include OrganisationHelper

  before_action :enforce_ssl, :except => [:health] 

  def enforce_ssl
    if ENV['ENFORCE_SSL'].to_s.eql?('true') && !request.ssl?
      redirect_to request.url.gsub(/http/i, "https")
    end
  end 


end

如果您在AWS ECS Fargate上运行应用程序并使用健康检查,则需要一个更灵活的解决方案,因为来自AWS目标组的健康检查不通过https调用。当然,您希望健康检查正常工作,同时希望强制对所有其他控制器方法使用SSL。 ENFORCE_SSL只是一个环境变量,用于开启/关闭此功能。

如果只是用于健康检查或指标端点,仍然可以使用 config.force_ssl = true,然后使用 config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } } 以便从重定向到 SSL 中排除对健康检查的请求。 - defsprite

3

这个答案与原问题有些不相关,但我记录下来以防其他人遇到类似的情况。

我遇到了这样一种情况:即使所有请求的来源都是未加密的(http),我需要让Rails在url helpers等方面使用https协议。

通常,在这种情况下(当Rails位于反向代理或负载均衡器等后面时很常见),反向代理或其他组件会设置x-forwarded-proto标头。因此,即使代理和Rails之间的请求未加密(顺便说一句,这在生产环境中可能不可取),Rails也认为一切都在https中。

我需要在ngrok tls隧道后运行。我想让ngrok使用我指定的letsencrypt证书终止tls。然而,当ngrok这样做时,ngrok不提供自定义标头的功能,包括设置x-forwarded-proto(尽管该功能将在未来某个时候推出)。

解决方案非常简单:Rails不依赖于来源的协议或是否直接设置x-forwarded-proto,而是依赖于Rack env var rack.url_scheme。因此,在开发中,我只需要添加这个Rack中间件:

class ForceUrlScheme
  def initialize(app)
    @app = app
  end

  def call(env)
    env['rack.url_scheme'] = 'https'
    @app.call(env)
  end
end

2
在Rails 4中,可以使用force_ssl_redirect before_action来强制单个控制器使用ssl。请注意,通过使用此方法,您的cookie将不会标记为安全,并且不使用HSTS

1
请问您能详细解释一下最后一句话吗?对于不熟悉“将cookie标记为安全”和HSTS的人来说。 - max pleaner

0

..._url 中添加 protocol

redirect_to your_url(protocol: 'https')

或者使用子域名

redirect_to your_url(protocol: 'https', subdomain: 'your_subdomain')

-2

相对 URL 根据定义使用当前协议和主机。如果您想更改使用的协议,您需要提供绝对 URL。我会采纳 Justice 的建议并为您创建一个执行此操作的方法:

def redirect_to_secure(relative_uri)
  redirect_to "https://" + request.host + relative_uri
end

-3

打开包含redirect_to的类,并添加一个名为redirect_to_secure_of的方法,然后实现它。最后调用:

redirect_to_secure_of "/some_directory/"

将这个方法放在lib目录或其他有用的地方。

谢谢,但我需要比“适当的实现”更详细的信息。 - Brian D'Astous
抱歉,我以为你在问如何使“调用”代码更简洁。 - yfeldblum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接