【问题标题】:How to authenticate a user on a domain (with ssl) and redirect and create the session in a different domain?如何验证域上的用户(使用 ssl)并在不同域中重定向和创建会话?
【发布时间】:2016-07-07 05:29:55
【问题描述】:

我有一个多租户 Rails 应用程序,可让帐户使用自己的自定义域。该应用程序托管在 Heroku 上,并且父域具有 ssl 证书。我希望我的自定义域用户能够使用父域 (www.foo.com) 登录并被重定向到他们的自定义域 (www.bar.com) 当用户访问时如何在自定义域上保持会话登录?

此功能与 Shopify 的工作方式非常相似。

【问题讨论】:

  • 请详细说明一下,自定义域是子域吗?租户是否都使用相同的 Rails 应用程序实例? HTTPS?
  • 嗨。所有帐户都使用相同的 rails 实例。父域有 ssl,自定义域没有。这就是为什么我想通过foo.com(带有ssl的域)为用户签名并将他们重定向到bar.com
  • 好的 - 所以通过 CloudFlare 等方式要求您的租户使用 SSL 不是一种选择?这里的重点是,即使您通过 SSL 保护登录凭据,会话 cookie 也可能在登录后被中间人拦截。
  • 是的,这是有可能发生的。还有其他方法吗? Shopify 是如何做到的?他们似乎使用了我在这里尝试的相同方法。如何使用 Cloudfare,您能解释一下吗?
  • 我查看了 Shopify,他们似乎使用子域作为管理面板(例如 yourstore.myshopify.com),该商店在自定义域下运行,但它只是以最少的逻辑提供内容。例如,结帐通过checkout.shopify.com 运行。

标签: ruby-on-rails session ssl heroku authlogic


【解决方案1】:

让我们看看 shopify 以及他们是如何做到的。

Shopify 区分两种情况:

  • 租户的域有 SSL
  • 租户的域没有 SSL

案例 #1 - 租户的域具有 SSL

租户虚拟域名:https://www.secure.shop

注册表单指向https://www.secure.shop/signup,成功注册后我会收到302 Found,它将我重定向到https://www.secure.shop并设置会话cookie。

-> POST https://www.secure.shop/signup, (signup data)
<- 302 Found 
   Location: https://www.secure.shop
   Set-Cookie: _session_id=eba010959d42ec1b734c7bc335ca13cb; path=/;secure; HttpOnly
-> GET https://www.secure.shop
<- 200 OK

登录表单指向https://www.secure.shop/login,成功登录后,我得到一个302 Found,它将我重定向到https://www.secure.shop并设置一个会话cookie。

-> POST https://www.secure.shop/login, (credentials)
<- 302 Found 
   Location: https://www.secure.shop
   Set-Cookie: _session_id=238aba8be83ceb3ba4a8ae4d94b1b026; path=/;secure; HttpOnly
-> GET https://www.secure.shop
<- 200 OK

签出通过https://www.secure.shop/checkout进行。

注销指向https://www.secure.shop/logout,发生的情况是:

-> GET https://www.secure.shop/logout
<- 302 Found
   Location: https://www.secure.shop
   Set-Cookie: _session_id=3b778bb251e170a9e3b1cd8794862203; path=/;  secure; HttpOnly
-> GET https://www.secure.shop
<- 200 OK

结论:一切都在租户的域下运行。一个会话 cookie,不涉及任何魔法。

案例 #2:租户的域没有 SSL

租户虚拟域名:http://www.insecure.shop

注册表单指向https://insecureshop.myshopify.com/account,当我创建新帐户时,会发生以下情况:

-> POST https://insecureshop.myshopify.com/account, (signup data)
<- 302 Found 
   Location: http://www.insecureshop.com/account?sid=514d3e699fd55ddb7c12398405e65abf
   Set-Cookie: _secure_session_id=c31d544b27ee8a49b5a6cf9e303e6829; path=/; secure; HttpOnly
-> GET http://www.insecureshop.com/account?sid=514d3e699fd55ddb7c12398405e65abf
<- 200 OK 
   Set-Cookie: _session_id=18d5f07e1e61d8707e111879860abad6; path=/; HttpOnly

登录表单指向https://insecureshop.myshopify.com/account/login,发生的情况是:

-> POST https://insecureshop.myshopify.com/account/login, (credentials)
<- 302 Found
   Location: http://www.insecure.shop/account?sid=a232e58b8cb9fb4936ddf889ab7e73e4
   Set-Cookie: _secure_session_id=d54a653cf4fcb66b831968e9e669b005; path=/; secure; HttpOnly
-> GET http://www.insecure.shop/account?sid=a232e58b8cb9fb4936ddf889ab7e73e4
<- 200 OK
   Set-Cookie: _session_id=44e041cdbcb64d2a2281bb64db52ada0; path=/; HttpOnly

结帐通过https://checkout.shopify.com

进行

注销指向http://www.insecure.shop/account/logout,发生的情况是:

-> GET http://www.insecure.shop/account/logout
<- 302 Found
   Location: https://insecureshop.myshopify.com/account/logout?sid=d6c774d39307def7f772de31031c665c
   Set-Cookie: _session_id=8dfb0a130d6f479d1af3a52c40ad3be6; path=/; HttpOnly
-> GET https://insecureshop.myshopify.com/account/logout?sid=d6c774d39307def7f772de31031c665c
<- 302 Found
   Location: http://www.insecure.shop
   Set-Cookie: _secure_session_id=18fcde259616586f89831399cc9c2425; path=/; secure; HttpOnly
-> GET http://www.insecure.shop
<- 200 OK

结论:在不安全的商店的情况下,一切都完成了两次,两个单独的(并且不同的!)会话在租户的不安全域上创建一次,在租户的 myshopify 子域上创建一次。两个会话都指向后端的相同用户记录。

凭据以加密方式发送,一次性令牌通过重定向传递到不安全域,然后在不安全域上创建经过身份验证的会话。此令牌以纯文本形式传输。

你首先想到的可能是:

如果中间人截获该令牌并劫持会话怎么办?

好吧,攻击者将在http://www.insecure.shop 上进行身份验证,但不会在https://insecureshop.myshopify.com 上进行身份验证。

如果我们尝试聪明一点并使用带有 CORS 的 AJAX 请求并使用 JavaScript 手动设置会话 cookie,那么不会发生使用魔法令牌的重定向

这也无济于事,因为会话 cookie 本身一直以纯文本形式传输,因此会话无论如何都可能被劫持。更糟糕的是,我们只有一个会话。

为什么他们对同一个用户使用两个不同的会话/会话 ID?

这就是神奇之处,您在http://www.insecure.shop 上的会话很容易被攻破,但是您在https://insecureshop.myshopify.com 上的会话不会被攻破,因为攻击者不知道会话 ID,因为 cookie 是通过 SSL 和会话 ID 传输的与“不安全”的不同。

但攻击者仍然可以在 http://www.insecure.shop 上滥用我的帐户

没错,但他能做什么?他可以将产品添加到您的购物车、阅读您的个人资料等,但他不能进行结帐并从您的信用卡中扣款。为什么?因为结帐通过https://insecureshop.myshopify.com 的安全部分,他没有会话cookie,因此未经身份验证。

但如果攻击者很聪明,他可以在不安全和安全部分更改密码并重新登录

如果您添加适当的措施,即要求用户输入密码以进行任何配置文件更改,则不会。攻击者不知道凭据,因为它们是通过 SSL 传输的。

还是没有更好的解决方案

有 - 在任何地方都使用 HTTPS,例如 CloudFlare 可以很容易地将 SSL 放在客户的域前面。这既可以减少您实施的开销,又可以为您的客户和客户的客户增加价值。双赢局面。

您不必为此使用 CloudFlare 等第三方解决方案,因为您负责 - 所有流量都通过您的服务器/前端代理(例如 nginx)。您可以为您的客户管理 SSL 证书并向他们收费,但是由于每个域都需要自己的证书,因此这对于记帐和配置都变得相当麻烦。

更新(重要)

请注意不安全情况下的细微差别,两个 cookie 的名称为 _session_id_secure_session_id。这是有充分理由的,因为两个会话都存在于同一个 rails 实例上,并且它们可以互换使用,这是一件坏事。我认为他们正在做的是在会话上设置一个标志,是否会话是通过安全通道创建的,并在之前添加适当的操作,例如

before_action :require_secure_session, only: :checkout

def require_secure_session
  head :unauthorized unless session[:is_secure_flag]
end

来源:

店铺示例取自http://wemakewebsites.com/blog/80-best-shopify-stores-for-ecommerce-inspiration,https 一个是#79,http 一个是#23。使用的工具:Chrome 开发者工具(网络标签)、cURL。

【讨论】:

  • 我正在使用 rack-cors gem 来处理跨域请求,但我在日志中不断收到无法验证 CSRF 令牌真实性的消息。如果我在控制器上执行 skip_before_action :verify_authenticity_token 一切正常。这应该是这样做的方法吗?让事物处理来自各处的请求?我可以编写 rack-cors 配置中允许的原始请求。我想应该这样做,但它应该是动态的。
【解决方案2】:

You may find this answer helpful. 在自定义域的重定向 URL 或 HTTP 请求中,您可以传入一个令牌,该令牌会被自定义域的 rails 应用程序捕获以登录用户。

【讨论】:

  • 所以基本思路是在 url 中发送单个访问令牌并使用它来登录用户。我用这种方法看到的问题是令牌将在未加密的情况下发送并且可能被嗅出。我说的对吗?
  • 理论上是的,尽管您可以将令牌设置为立即过期以使攻击难以执行。另一种方法是使用会话 cookie 来更改您的站点对其他来源的域的响应方式。这个答案可能会有所帮助:stackoverflow.com/questions/5123325/…
  • 那篇文章主要讨论子域(这很简单)。你想让我在那里读一些具体的东西吗?我想知道shopify是如何做到的。他们是我所知道的唯一处理几乎相同功能的人。
【解决方案3】:

我创建了一个具有类似功能的应用。我处理这个的方式是有一个简单的重定向。您可以在创建帐户时将子域字符串保存到用户帐户,然后当用户成功登录时,您可以像这样重定向它们

    if current_user.subdomain?
      redirect_to root_url(subdomain: current_user.subdomain)
    else
      redirect_to root_url, notice: "Logged in!"
    end

【讨论】:

  • 谢谢,但这不是我们所要求的。
【解决方案4】:

我认为有两种方法可以做到这一点:

1.从父域返回时,通过请求标头将用户会话信息发送到自定义域。

示例代码应该如何将会话存储到 redis 我们可以通过标头将其发送到其他服务器:

def after_sign_in_path_for(resource_or_scope)
  #store session to redis
  if current_user
    # an unique MD5 key
    cookies["_validation_token_key"] = Digest::MD5.hexdigest("#{session[:session_id]}:#{current_user.id}")
    # store session data or any authentication data you want here, generate to JSON data
    stored_session = JSON.generate({"user_id"=> current_user.id, "username"=>current_user.screen_name, ... ...})
    $redis.hset(
      "mySessionStore",
      cookies["_validation_token_key"],
      stored_session,
     )
   end
end

2。让用户通过会话服务通过代码强制登录,以便在与同一用户的父域相同的自定义域上启动会话。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-03
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 2015-11-20
    • 1970-01-01
    • 2022-08-24
    相关资源
    最近更新 更多