在Rails中如何在子域之间共享会话(cookies)?

100

我有一个应用程序设置,其中每个用户都属于一个公司,并且该公司有一个子域名(我正在使用basecamp风格的子域名)。 我面临的问题是,Rails正在创建多个cookie(一个用于lvh.me,另一个用于subdomain.lvh.me),这在我的应用程序中造成了相当多的问题(例如,登录后闪存消息在所有请求中保持不变)。

我在我的 /config/initializers/session_store.rb 文件中有以下内容:

AppName::Application.config.session_store :cookie_store, key: '_application_devise_session', domain: :all

"domain: :all" 似乎是我在 Google 上找到的标准答案,但它对我不起作用。感谢任何帮助!

9个回答

81
事实证明,“domain: all”会为在同一会话期间访问的所有不同子域创建一个cookie(并确保它们在请求之间传递)。如果没有传递域参数,则意味着为在同一会话中访问的每个不同域创建一个新的cookie,并且旧的cookie会被丢弃。我需要的是一个单一的cookie,即使域名更改也可以在整个会话中保持持久性。因此,在开发中传递“domain:“ lvh.me”解决了这个问题。这将创建一个在不同子域之间保留的单个cookie。
对于需要进一步解释的人,这是一个很好的链接: http://excid3.com/blog/sharing-a-devise-user-session-across-subdomains-with-rails-3/

2
谢谢,伙计。我在一个项目中一直面临这个问题。终于找到了解决方案。 - Shirjeel Alam
3
请确保在所有应用程序中使用相同的 config.secret_key_base,否则将无法解码 cookie。 - Bruno Buccolo
6
我没有看到与Rails 4相关的任何问题。你知道这个情况是否有变化吗?我无法在我的项目中使它工作。它一直在重新创建cookies。谢谢。 - Andy
如果我想使用CacheStore将会话存储在memcached中怎么办? - Amit Patel
4
在Rails4中,我发现这只适用于带有破折号的子域名,但对带有下划线的子域名无效:Appname::Application.config.session_store :cookie_store, key: '_appname_session', domain: :all, tld_length: 2 - user1515295
1
@user1515295 子域名不能有下划线,这是 DNS 标准,与 Rails 无关。 - hammady

72

http://excid3.com/blog/sharing-a-devise-user-session-across-subdomains-with-rails-3/

“在这里需要注意的是,如果您像某些地方建议的那样设置:domain => :all,除非您使用localhost,否则它根本不起作用。:all默认TLD长度为1,这意味着如果您正在使用Pow(myapp.dev)进行测试,也不会起作用,因为它是长度为2的TLD。”
换句话说,您需要:
 App.config.session_store ... , :domain => :all, :tld_length => 2

清除浏览器的Cookies也是一个好主意


1
这是最佳答案,因为一个更改适用于所有环境(app.com和app.dev)。不需要自定义中间件。另外清除cookies也是个好主意! - Turadg
1
你缺少了 , :tld_length => 2 - montrealmike
1
请务必在所有应用程序中使用相同的 config.secret_key_base,否则无法解码 cookie。 - Bruno Buccolo
4
在Rails 4中,:domain => :all 不起作用,尝试使用 domain => 'lvh.me', tld_length = 2。这对我有效。 - Minh Triet
1
在 Rails 4.2 中,我在使用 lvh.me 域名时只需使用 domain: :all, tld_length: 2,就能得到很好的结果。 - zwippie
显示剩余4条评论

26

我想找到一种解决这个问题的方法,而无需明确指定域名,这样我就可以在本地主机、lvh.me以及任何生产中使用的域之间跳转,而无需不断编辑session_store.rb文件。然而,设置“domain: :all”对我来说似乎没有起作用。

最终,我发现我需要在该表达式中指定tld_length(顶级域长度)。默认的tld_length为1,而例如example.lvh.me的tld_length为2,127.0.0.1.xip.io的tld_length为5。因此,在开发中的lvh.me子域和其他任何生产环境中,我在session_store.rb文件中所拥有的内容如下。

MyApp::Application.config.session_store :cookie_store, key: '_MyApp_session', domain: :all, tld_length: 2

希望这能帮助到某些人,因为我花了很长时间才找到这个答案!


19
在寻找将cookie设置为根域名的最简单方法时,我遇到了这个问题。当将:all选项作为域选项传递时,似乎有一些关于它的错误信息。对于大多数域名,它实际上会像预期的那样工作,将cookie设置为根域名(例如,在test.example.com中使用.example.com)。我认为大多数人遇到问题是因为他们在使用域名lvh.me进行测试。Rails用于查找顶级域名的正则表达式定义为DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/。如果您注意到最后一部分,您会发现Rails将lvh.me解释为类似于com.au的TLD。如果您的用例需要lvh.me有效,则:all选项将无法正常工作,但对于大多数域,它似乎是最简单和最佳的选择。
简而言之,假设您不在一个三字母域上开发(或任何混淆以上正则表达式的域名),正确的答案是使用:all选项。

谢谢,这最终帮助我理解了为什么有那么多答案推荐使用 tld_length 为 2,但我却不需要! - soupdog
这个答案需要排得更靠前一些。谢谢,先生。 - lucius
"lvh.me是类似于com.au的顶级域名。顺便说一下,Rails确实应该像对待国家域名(黑山)一样解释.me。" - mahemoff

19

由于某些原因,将:all替换为域名在我的情况下无法正常工作(rails 3.2.11)。需要使用自定义中间件来解决此问题。以下是该解决方案的摘要。

tl;dr:您需要编写自定义Rack中间件,并将其添加到conifg/environments/[production|development].rb中。这是针对Rails 3.2.11的。

Cookie会话通常仅存储在顶级域中。

如果您查看Chrome -> 设置 -> 显示高级设置… -> 隐私/内容设置… -> 所有cookie和站点数据… -> 搜索{yourdomain.com}您会发现sub1.yourdomain.comothersub.yourdomain.comyourdomain.com将分别显示为不同的条目。

挑战在于要跨所有子域使用相同的会话存储文件。

步骤1:添加自定义中间件类

这就是Rack中间件发挥作用的地方。以下是一些相关的rack&rails资源:

以下是您应该在lib中添加的自定义类 这是由@Nader编写的,大家都应该感谢他

# Custom Domain Cookie
#
# Set the cookie domain to the custom domain if it's present
class CustomDomainCookie
  def initialize(app, default_domain)
    @app = app
    @default_domain = default_domain
  end

  def call(env)
    host = env["HTTP_HOST"].split(':').first
    env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{@default_domain}"
    @app.call(env)
  end

  def custom_domain?(host)
    host !~ /#{@default_domain.sub(/^\./, '')}/i
  end
end

基本上这个做法的作用是将所有的cookie会话数据映射回与根域名相等的同一个cookie文件中。

第二步:添加到Rails配置中

现在你已经在lib中有了一个自定义类,请确保进行了自动加载。如果这对你来说意味着什么,可以看看这里:Rails 3 autoload

第一件事就是要确保你正在系统范围内使用cookie存储。在config/application.rb中,我们告诉Rails使用cookie存储。

# We use a cookie_store for session data
config.session_store :cookie_store,
                     :key => '_yourappsession',
                     :domain => :all

这里提到的原因是因为有一行代码 :domain => :all。有些人建议使用 :domain => ".yourdomain.com" 来代替 :domain => :all。但出于某些原因,这种方法对我不起作用,我需要像上面描述的那样使用自定义的 Middleware 类。

然后在你的config/environments/production.rb文件中添加:

config.middleware.use "CustomDomainCookie", ".yourdomain.com"

请注意,前面的点是必要的。有关原因,请参见“ 子域cookie是否可以在上级域请求中发送?”。

然后,在您的config/environments/development.rb文件中添加:

config.middleware.use "CustomDomainCookie", ".lvh.me"

lvh.me技巧映射到本地主机。非常棒。有关子域的更多信息,请参见此Railscast此注释

希望这样做就可以了。老实说,我不完全确定为什么这个过程如此复杂,因为我觉得跨子域站点很常见。如果有人对每个步骤背后的原因有更深入的了解,请在评论中启迪我们。


有没有办法使这个适用于多个顶级域名?我有一个在不同国家运行的产品。这里我们假设默认域名是yourdomain.com,但如果它应该适用于.be .sv .fr .com.br .com.ar等其他域名怎么办?谢谢。 - Marc Lainez
我就是无法让它工作。我正在使用Rails 4进行开发,但它似乎完全忽略了上面的所有代码,并且不想在子域之间共享会话。 - Ole Henrik Skogstrøm
@OleHenrikSkogstrøm 请确保在所有应用程序中使用相同的 config.secret_key_base,否则它将无法解码 cookie。 - Bruno Buccolo

8

Rails 4.x(也适用于Rails 5/6版本)

如何在本地主机(Rails)中获取lvh.me:3000和子域名

开发: 我们可以通过将.lvh.me添加到session_store.rb中来共享cookie,

这会在localhost的子域名之间共享,例如 admin.lvh.me:3000, lvh.me:3000 等等...

#config/initializers/session_store.rb

domain = Rails.env.production? ? ".domain_name.com" : ".lvh.me"

Rails.application.config.session_store :cookie_store, 
                      key: '_app_name_session', domain: domain

4
你尝试过吗?
AppName::Application.config.session_store :cookie_store, key: '_application_devise_session', domain: 'lvh.me'  

基本上,我们建议使用单个cookie作为基本域名,而忽略子域名。尽管这种方法仍然存在一些缺陷...


2

支持Rails 5。

如果您希望它能在任何域名下运行:

Rails.application.config.session_store :cookie_store, key: '_my_app_session', domain: :all, tld_length: 2

要针对每个环境进行配置,您可以使用以下方法:

Rails.application.config.session_store :cookie_store, key: '_my_app_session', domain: {
  production: '.example.com',
  development: '.example.dev'
}.fetch(Rails.env.to_sym, :all)

参考:https://github.com/plataformatec/devise/wiki/How-To:-Use-subdomains

本文介绍如何在Rails项目中使用子域名。通过配置devise gem和路由,我们可以实现在同一应用程序中处理多个子域名。这个功能允许我们在一个网站上创建不同的用户身份验证和授权策略。


0
如果您正在使用Redis作为会话存储。
if Rails.env.development?
    Rails.application.config.session_store :redis_store, {
       servers: [
        { host: 'localhost', port: 6379},
      ],
      key: '_app_session',
      expire_after: 1.day,
      domain: :all
    }

else
    Rails.application.config.session_store :redis_store, {
       servers: [
        { host: HOST_URL, port: PORT},
      ],
      key: '_app_session',
      expire_after: 1.day,
      domain: '.domain.com',
      tld_length: 2
    }
    
end 

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