如何在多个域之间创建共享登录服务?

43

我想知道如何实现共享跨域登录系统,以及需要采取的最佳实践和安全预防措施。如果您熟悉37Signals,您可能习惯于使用共享的通用认证机制,通过顶级导航到达不同的产品后,您不必再次登录。我希望以类似的方式实现某些东西。

我在网上找到的最接近的东西是维基百科关于中央认证服务的条目,以及对跨域登录——当从一个域传输到另一个域时如何自动登录用户的回答,但在这种情况下可能略有不同。

我检查了他们的会话Cookie,以便了解他们在过程中所做的事情。最初,每个产品链接都有一个“goto”uristub,例如:

https://MY_COMPANY.campfirenow.com/id/users/[int_identifier]/goto

使用FireCookie和Firebug的NET选项卡,我可以看到设置的Cookie和在过程中发生的重定向。 goto url触发302重定向到:

https://MY_COMPANY.basecamphq.com/login/authenticate?sig=[BASE64_ENCODED_AND_ENCRYPTED_DATA]

会话标识符被重新创建,很可能是为了防止CSRF攻击。一些Cookie数据和GET参数sig的部分内容已使用base64_decode进行了部分解密,如下所示:

// sig GET param
array(2) {
  [0]=>
���ף�:@marshal_with_utc_coercionT7�z��<k��kW"
  [1]=>
  string(18) "���k�<kn8�f���to��"
}

// _basecamp_session cookie session param
string(247) {
:_csrf_token"1Sj5D6jCwJKIxkZ6oroy7o/mYUqr4R5Ca34cOPNigqkw=:session_id"%060c0804a5d06dafd1c5b3349815d863"
flashIC:'ActionController::Flash::FlashHash{:
@used{: auth{"
              MY_COMPANY{:
                       user_idi�3
:identity_idi�W����������s�]��:�N[��:

编码使代码块出现了问题。感谢您的帮助!


一个预打包的共享登录服务,例如OpenID、Facebook、OAuth或Google,是否适合您?或者,您绝对需要自己的共享登录服务吗? - Flipster
7个回答

78

我认为你问题的关键在于你写道:“如果你使用顶级导航到达不同的产品,则不需要随后登录”,我来解释一下:

你想要从site1.com移动到site2.com,并让site2.com知道你是通过site1.com登录的某个用户。

由于cookie不能在不同的域之间共享(除了子域,但我猜你不是在谈论子域),因此你必须通过链接传递一些信息给site2.com,这将让它与其后端通信并知道你是什么用户。

让我们从一个天真的解决方案开始,然后逐步解决它所带来的一些问题:

假设你有一个用户表格在某个后端数据库中,并且你的用户有一个ID。

现在假设用户在site1.com上验证,他是你数据库中的用户123。

最天真的解决方案是调用site2.com/url/whatever?myRealID=123,然后让site2检查这个ID并“相信”用户这确实是他。

问题:任何人(甚至是你网站的有效用户)都可以看到你的链接并创建一个带有myRealID=123或尝试其他值的链接。 site2.com会接受他作为该用户。

解决方案:让我们不使用可猜测的ID。假设你在用户表中添加一个唯一的GUID,如果用户123具有GUID为8dc70780-15e5-11e0-ac64-0800200c9a66,则调用site2.com/url/whatever?myGuid=8dc70780-15e5-11e0-ac64-0800200c9a66。

新问题:尽管很少有人会猜到你的用户的GUID,但他的GUID仍然可能被某个中间人劫持,看到这个链接并且如果某个人获得了GUID,他可以永远使用它。

解决方案:使用保存在您服务器上的私钥对一个包含以下数据项的字符串进行签名,即当前时间戳、目标站点(即“site2.com”)、所述GUID,此签名可被翻译为“这是证明该链接是由该站点在所述时间为具有此GUID的用户创建,以便该域进行身份验证”的证据,并将其发送到site2.com以及时间戳和GUID。现在,当site2.com获取该链接时,他可以确保它被正确签名,如果某个中间人或最初的用户试图以任何方式更改它(无论是通过修改时间还是GUID),那么签名将不匹配,site2.com将拒绝对用户进行身份验证。 最后一个问题:如果该链接被中间人截获,他仍然可以从其他机器使用它。 解决方案:向传递的参数添加一个nonce。Nonce只是一个随机数,您的认证系统应确保它不允许您使用此号码进行多次认证(因此称为Number-ONCE)。请注意,这意味着您在site1.com上创建的每个指向site2.com的链接都应具有不同的nonce,后者需要保存在后端以供以后验证。 因为您不想永远向此nonce表添加记录,所以通常会决定此类链接仅在创建后的一段给定时间内有效。因此,您可以创建早于此时间限制的nonce记录。 我希望这就是您要寻找的大纲。可能我漏掉了一些东西,但这些都是基本指南:使用签名证明链接中的数据的真实性,并使用nonce防止中间人攻击。如果可能,我还建议对这些链接使用HTTPS。 Eyal

1
为了进一步实现共享密钥:为共享登录系统中的每个域添加包含公私钥对的配置变量。如果所有框的签名验证方法相同,您可以验证签名并甚至使用mcrypt()或等效库传输双向加密数据以增强验证签名的安全性(其中签名本质上是数据的校验和)。 - Corey Ballou
为什么不仅使用 nonce(与后端会话 ID 相关联)?我的意思是只需创建一个令牌(可能在 1-2 分钟内过期,仅可使用一次),它与后端会话 ID 相关联,因此当我在 site2 上使用它时,服务器可以从数据库中获取会话 ID、擦除令牌并向浏览器发送 cookie,使浏览器具有与 site1 上相同的会话。这种方法有哪些缺点? - mettjus
2
至少有一个区别是,在每次尝试攻击时,您必须去访问数据库,这样会比仅验证数据是否正确签名并且仅在确实正确签名后才访问数据库更耗费资源。 - epeleg
你还可以拥有 nonce 和其经过签名的版本,这样 site2.com 就可以验证签名,并在验证成功后调用 SSO 服务器。这种替代方案怎么样? - kheraud
我不确定我理解你的问题。正如我(将近6年前 :))所写的那样,nonce 应该是传递参数的一部分。因此,在创建签名时应考虑它。(这样它就不能被中间人操纵。话虽如此,我也可以看到不签署 nonce 本身的有利点... - epeleg

5

您可以尝试实现一个类似 Stack Overflow 的全局网络自动登录解决方案。
关于其杰出的实现,Kevin Montrose在这里有一篇深入的帖子。

它需要 Cookies、HTML5 localStorage、Javascript、Iframe 和许多安全检查,但我认为它是值得的。


1

允许用户选择使用他们的Facebook / Yahoo / Gmail / OpenID解决方案是一个开始,但这并不能最终解决实施或使用自己的解决方案的问题。

过去曾经开发和管理过一种实现方式(我们最终采用了SiteMinder,但我猜您不想要真正产品的成本),我认为Eyal有一个非常好的想法,但我会稍微加以改进。

在认证时生成一个随机代码(可以是GUID值,但它不是每个用户的静态值),该代码与用户ID一起存储在事务表中。此代码具有最大生命周期(您需要判断该时间段的安全值)。此代码理想情况下应与用户名哈希或加密,并作为URL的一部分发送。


0

你尝试过像Charles这样的工具来嗅探来回发送的内容吗?它或许能够更详细地分析问题。

正如FlipScript所提到的,也许使用OAuth/OpenID是你前进的一种方式?


0

看一下Jasig CAS http://www.jasig.org/cas。我认为它可以满足你的需求。它是开源的,并且有许多插件,可以让你使用多个身份验证来源和多个域名/语言进行身份验证。


0

为什么不使用Open ID来解决这个问题。

OperID将是解决这个问题的最佳方案。


3
Open ID是一种很好的身份验证方式,但它并不能解决跨站点登录的问题。例如,如果你使用Open ID登录siteA.com,你仍然需要使用上述方法来创建一种情况,在这种情况下你也已经登录到了siteB.com。顺便说一下,如果你想要能够通过常见的社交网络进行身份验证,你也可以使用gigya.com提供的解决方案(我最近刚离开那里工作)- 但同样地,这并不解决跨域问题,你需要自己处理。 - epeleg

0

使用类似于.NET Passport(例如Windows Live ID)或Facebook在其API中所做的方式,这些方式可以公开查看。


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