如何验证域上的用户(使用 ssl)并在不同的域中重定向和创建会话?

How to authenticate a user on a domain (with ssl) and redirect and create the session in a different domain?

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

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

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

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

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

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

1.当从 parent 域返回时,通过请求 headers 将用户 session 信息发送到自定义域。

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

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。让用户通过 session 服务通过代码强制登录,以在与同一用户的 parent 域相同的自定义域上启动 session。

让我们来看看 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 与 "insecure" 不同。

但攻击者仍然可以在 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。