使用Rails和Devise在用户注册后将用户登录到他们的子域名

16

我在我的Rails 3应用程序中使用Devise进行身份验证。该应用程序使用PostgreSQL模式和Apartment gem来实现多租户。

在创建帐户之后,针对特定子域名的登录和注销运作良好。用户只能登录其特定帐户的子域名,这很好。

这是我遇到问题的地方......

全新用户访问注册URL:

http://foo.com/signup

默认情况下,当他们点击提交时,会创建一个新帐户,但是用户会被送到:

http://foo.com/dashboard

相反,我希望他们转到:

http://myaccount.foo.com/dashboard

为了实现这一点,我在registrations_controller.rb文件中覆盖了after_sign_up_path_for方法:

def after_sign_up_path_for(resource)
  root_url(:subdomain => resource.account.subdomain)
end

这个功能按照设计预期工作--它加载了正确的URL--但是用户的会话被创建在根域名(foo.com)而不是子域名下,因此用户需要登录。

我发现一个建议是更改 config/initializers/session_store.rb 为:

config.session_store :cookie_store, :key => '_domain_session', :domain => :all

但这样允许任何人登录到任何子域名的帐户,显然是不好的。

问题:我如何确保在注册过程中创建的会话对于创建的子域有效?


你需要像你在问题中所写的那样更改cookie,否则用户将始终在子域中未知。一个简单的解决方案是编写一个before-filter或扩展现有的登录逻辑以检查正确的子域。这通常是必须要做的,因为您不希望依赖cookie进行身份验证。 - phoet
@phoet 嗯,我考虑过了。那么我应该做 :domain => '.mydomain'(允许跨子域cookie),然后修改我的 before_filter 来检查正确的子域吗? - Rob Sobers
我使用了一个中间件来处理Cookie:https://github.com/phoet/on_ruby/blob/master/app/middlewares/cookie_domain.rb - phoet
@phoet 我不确定你的中间件示例是否适用于我,因为cookie仍在用户位于根域时。或者它会在用户到达子域时重新设置cookie吗?我遇到的最大问题是让Devise在子域上进行额外的检查。有什么建议吗?我认为如果我能实现这一点,我将启用跨子域cookie,并依靠我的授权代码来确保安全性。 - Rob Sobers
1
before_filter是一个不错的选择,或者可以添加一个Warden策略来检查子域名以验证用户的有效性。您还可以使用Warden钩子,如set_userafter_authentication来实现相同的功能。 - Viren
2个回答

23
你可以在你的config.session_store中使用 "domain: :all" 选项,并像评论中建议的那样添加一个before_action。所以你仍然需要将代码放在config/initializers/session_store.rb或config/application.rb中:
config.session_store :cookie_store, :key => '_domain_session', :domain => :all

然后在你的application_controller中添加以下代码:

#app/controllers/application_controller.rb
before_action :check_subdomain

def check_subdomain
  unless request.subdomain == current_user.account.subdomain
    redirect_to root_path, alert: "You are not authorized to access that subdomain."
  end
end

1
这个可以工作,但有一个小问题:如果我将会话移动到数据库(如session_store.rb所建议的),当我创建一个全新的帐户(在http://example.com)时,我会被重定向到http://foo.example.com并且已经登录(很好!)。然而,我下一个操作就会强制要求我再次登录。如果我将会话保留在cookie中,则会话会按预期持续存在。你有什么想法? - Rob Sobers
你正在使用哪个版本的Rails?在Rails 4中,activerecord-session_store已被移除。唯一可用的选项是::cookie_store、:mem_cache_store和:disabled。如果您的会话数据超过4kb,则最好使用:mem_cache_store。否则,最好使用:cookie_store。不建议使用sql数据库进行会话,因为它速度较慢,并且对其造成了不必要的负载。这里有一篇关于它的文章:http://blog.remarkablelabs.com/2012/12/activerecord-sessionstore-gem-extraction-rails-4-countdown-to-2013 - Gjaldon
@RobSobers,如果我的回答有帮助,请让我知道,或者如果您需要任何澄清,请告诉我。 - Gjaldon
我已经准备好了,谢谢!最终我选择了稍微不同的解决方案,原因我会在问题中补充说明,但除此之外,你所提供的方法完全可以胜任。 - Rob Sobers
@Gjaldon,我正在尝试不使用devise来做同样的事情。你能指导我该怎么做吗? - Mohd Anas
@Gjaldon,我按照您说的做了,但是它给我返回了错误信息“undefined method `subdomain' for nil:NilClass”。before_action 对于每个操作都有效,您能帮我解决一下吗?在注册后,我想要绕过 sign_in,URL 应该是 live pqrst.example.com/dashboard。 - thedudecodes

2

覆盖devise会话控制器。

创建一个文件,路径为app/controllers/devise/sessions_controller.rb

在该控制器中覆盖sessions_controller类。将链接中的代码粘贴进去。https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb

class Devise::SessionsController < DeviseController
 # copy-paste the devise session controller below.
 ...
end

根据您的需求编辑create操作。

def create
  self.resource = warden.authenticate!(auth_options)
  set_flash_message(:notice, :signed_in) if is_flashing_format?
  sign_in(resource_name, resource)
  yield resource if block_given?
  respond_with resource, :location => after_sign_in_path_for(resource)
end

我想看看是否能找出如何使它正常工作,但我确定你想要的结果可以通过覆盖devise会话控制器来实现。
编辑
如果您正在使用跨子域cookie,则可以通过before_filter强制执行子域会话。例如:
before_action do 
    redirect_to root_path, alert: 'That subdomain does not belong to you' if request.subdomain != current_user.subdomain
end

在哪里编写这个 before_action?将其编写在应用控制器中会在调用任何方法时都调用它,并且会导致错误。 - thedudecodes

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