6

我遇到了一个问题,希望您能帮助解决。假设我在一家名为“Blammo”的假设公司工作,我们有一个名为“Log”的假设产品。我正在尝试建立一个系统,有人可以登录到 logfromblammo.com 并订购我们的一些产品,然后当他们准备购买时,去 checkout.blammo.com 支付他们的订单。最终我想让 Blammo 推出一个新的假设产品,它有自己的网站:rockfromblammo.com,并让该网站也能够与 checkout.blammo.com 共享会话,这样用户就可以在这两种产品中拥有一个购物车网站。

当然,上面描述的假设场景并不是我公司的实际运作方式,但它是我需要做的一个很好的例子。我们有一个现有的用户数据库,我们有办法在我们的任何站点上验证我们的任何用户,但我的目标是允许用户从一个站点无缝地跨越到另一个站点,而无需重新验证。这也将允许我们将购物车等数据无缝传输到结帐站点。

我(简要地)查看了 OpenID 等解决方案,但我需要能够将我们拥有的任何解决方案与我们现有的身份验证方法集成在一起,这并不是非常强大。有没有什么好方法可以单独通过 PHP 做到这一点?

4

5 回答 5

8

您可以做的是在站点之间创建“交叉”链接以进行会话。

最简单的方法是通过查询字符串传递会话 ID;例如

http://whateverblammo.com/?sessid=XXYYZZ

在您开始认为任何人都可以捕获该信息之前,请考虑您的 cookie 是如何传输的;假设您没有使用 SSL,那么对于使用网络的人来说并没有太大区别。

这并不意味着它是安全的。一方面,用户可能会不小心复制/粘贴地址栏,从而泄露他们的会话。为了限制这种暴露,您可以在收到页面后立即重定向到没有会话 ID 的页面。

请注意,mcrypt()在会话 ID 上使用不会有太大帮助,因为问题不是值的可见性;会话劫持不关心底层价值,只关心它的 url 的可重复性。

您必须确保该 id 只能使用一次;这可以通过创建一个跟踪使用计数的会话变量来完成:

$_SESSION['extids'] = array();

$ext = md5(uniqid(mt_rand(), true)); // just a semi random diddy
$_SESSION['extids'][$ext] = 1;

$link = 'http://othersite/?' . http_build_query('sessid' => session_id() . '-' . $ext);

收到时:

list($sid, $ext) = explode('-', $_GET['sessid']);
session_id($sid);
session_start();
if (isset($_SESSION['extids'][$ext])) {
    // okay, make sure it can't be used again
    unset($_SESSION['extids'][$ext]);
}

每次跨越边界时都需要这些链接,因为会话可能自上次以来已重新生成。

于 2012-12-10T18:37:39.900 回答
4

可以做到,但不能使用简单的 cookie,而且这不是微不足道的。您所追求的是单点登录 (SSO) 解决方案,类似于 Google 在 i.google.com、gmail.com、youtube.com 等上共享登录的解决方案。

我过去曾使用 OpenID 来实现这一点。

基本思想是拥有一个单一的身份验证域(提供者),每当其中一个站点(消费者)想要对用户进行身份验证时,它们会将它们重定向到身份验证域。如果他们没有登录,他们可以使用您需要的任何详细信息登录。

如果他们已经登录(即使从不同的目标站点),他们不需要再次登录。

然后,用户会被发送回目标站点,并在 url 中添加一个令牌。目标站点的服务器使用此令牌来验证用户是否已通过身份验证服务器进行身份验证。

这是一个非常简单的解释。做到这一点并不困难,安全地做到这一点更是如此。安全地生成和验证令牌的细节是具有挑战性的部分。这就是为什么我建议构建一个设计良好的系统,比如 OpenID。

于 2012-12-10T18:38:54.153 回答
3

在跨域 Ajax 中,您可能会发现跨域请求丢失了 cookie 以及随后的会话。如果您要从站点example.com向子域s2.example.com进行 ajax 调用,您将需要在 PHP 的标头中使用属性:

header('Access-Control-Allow-Origin: https://example.com');
header('Access-Control-Allow-Credentials: true');

在JS中你要添加

xhrFields: { withCredentials: true }

否则 cookie 不会通过,您将无法在子域上使用会话。

对子域的完整 JS 请求将不会丢失会话

$.ajax({
    url: "https://s2.example.com/api.php?foo=1&bar=2",
    xhrFields: { withCredentials: true },
    success:function(e){
        jsn=$.parseJSON(e);
        if(jsn.status=="success") { 
                alert('OK!');
        } else {
                alert('Error!');
        }
    }
}) 
于 2017-12-27T14:02:19.653 回答
1

您需要像这样设置会话 cookie 域:

session_set_cookie_params($lifetime,$path,'.site.com')

这仅适用于站点位于相同域名(包括TLD(顶级域))上的情况。

请参阅此处了解更多信息

或者,如果您正在考虑尝试跨域访问会话,例如 from site1.netto site2.com,则无法做到这一点。

于 2012-12-10T18:21:19.513 回答
0

如果您的网站可以依赖 Javascript 来运行,那么您大概可以执行以下操作:

假设您在 blammo.com 上有一个会话,并且您想从 rockblammo.com 访问它。在 rockblammo.com 页面上,您可以加载<script>from blammo.com/get-session.js,这将(从服务器端)返回会话 ID。一旦返回,您<script>在页面中插入一个新标签,指向rockblammo.com/set-session.js?sessionId=XXX,其中 XXX 是您刚刚从 blammo.com 获得的会话 ID。现在,在rockblammo.com 的服务器端,会话cookie 被更新并设置为这个会话ID。展望未来,这两个页面现在将共享相同的 session-id,并且假设它们可以访问后端的相同会话存储,它们将是同步的。

例如,输出blammo.com/get-session.js将是:

var sessionId = "XXX";
var s = document.createElement("script");
s.src = "/set-session.js?sessionId=" + escape(sessionId);
document.body.appendChild(s);

的输出rockblammo.com/set-session.js将为空白,但将包含一个 http 标头,例如:

Set-Cookie: sessionId=XXX

如果您不想依赖 Javascript,您可以通过在两个站点之间来回重定向并在查询字符串参数(GET 参数)中传递 sessionId 来做同样的事情。

于 2012-12-10T18:46:37.367 回答