安全且灵活的跨域会话

6
我有一个问题,希望你可以帮忙解决。假设我在一个名为“Blammo”的虚构公司工作,我们有一个虚构的产品叫做“Log”。我正在尝试建立一个系统,让用户可以登录到logfromblammo.com网站上订购我们的产品,当他们准备好付款时,转到checkout.blammo.com网站上完成支付。最终,我想让Blammo推出一款自己的新产品,拥有自己的网站: rockfromblammo.com,并让该网站也能与checkout.blammo.com共享会话,以便用户可以在两个产品网站上拥有一个单一的购物车。
自然地,上述虚构场景并不是我的公司实际运作方式,但这是我需要做的事情的一个很好的例子。我们有一个现有的用户数据库,并且我们有办法在任何一个网站上对任何一个用户进行身份验证,但我的目标是允许用户在不重新进行身份验证的情况下从一个网站无缝地跨越到另一个网站。这也将使我们能够无缝地转移数据,例如购物车到结账网站。
我已经(简要地)研究了诸如OpenID之类的解决方案,但我需要能够将我们现有的身份验证方法与任何解决方案集成起来,而这种方法并不是非常强大。有没有好的方法只通过PHP实现这一点?

所以基本上你的网站有多个二级域名,而不是像 product.site.com 这样的吗? - Ja͢ck
2
我认为你在这里的最佳解决方案是让rockfromblammo.com重定向到rockfrom.blammo.com--安全在这类事情中是一个很大的问题,给两个不同的域名访问购物车会增加第三方找出如何做同样的事情的风险。 - Blazemonger
@Blazemonger 这不仅是安全风险,而且不能跨域完成。它只能在子域之间完成! - Ben Carey
1
@BenCarey 哦,我相信这是可以做到的。但这将需要重写整个购物车逻辑,并使系统变得完全不安全。 - Blazemonger
5个回答

9
你可以创建"跨站"链接来实现会话的传递。
最简单的方法是通过查询字符串传递会话ID;例如:
http://whateverblammo.com/?sessid=XXYYZZ

在开始认为任何人都可以获取这些信息之前,请考虑一下您的cookie是如何传输的;假设您没有使用SSL,那么对于截取网络的人来说并没有太大区别。
这并不意味着它是安全的;首先,用户可能会意外地复制/粘贴地址栏,从而泄露他们的会话。为了限制此类暴露,您可以在接收到会话ID后立即重定向到一个不带会话ID的页面。
请注意,对会话ID使用mcrypt()并不能起到太大作用,因为问题不在于值的可见性;会话劫持并不关心底层值,只关心其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]);
}

每次跨越边界时,您需要使用这些链接,因为会话可能已经在上一次之后重新生成。


1
这是一个旧答案,但值得注意的是,如果用户在跨域边界时共享屏幕,则存在潜在的安全问题。令牌将暴露在URL中,这可能会被用于劫持会话(如果会话存活时间很长)。 - Yona Appletree
那个潜在的安全问题已经在答案中提到了,不是吗? - Ja͢ck
啊,关于它只被使用一次的那部分。没错,我漏掉了。尽管现在许多系统使用无状态服务器,所以要在不使用数据库或其他外部存储的情况下跟踪使用计数是很棘手的。 - Yona Appletree

4
可以做到,但不是简单的cookie操作,也不是一件轻松的事情。你需要一个单点登录(SSO)解决方案,类似于谷歌跨i.google.com、gmail.com、youtube.com等共享登录信息的解决方案。
我过去使用过OpenID来实现这个功能。
基本思路是有一个单独的身份验证域(提供者),每当其中一个站点(消费者)想要认证用户时,它们将其重定向到身份验证域。如果他们没有登录,他们可以使用您需要的任何详细信息进行登录。
如果他们已经登录(即使来自另一个目标站点),他们就不需要再次登录。
然后,用户会带着URL中的令牌返回目标网站。该令牌由目标站点服务器用于验证用户是否已在身份验证服务器上进行了身份验证。
这只是一个非常简单的解释。实现这并不难,但确保安全性就更加困难了。生成和验证令牌的安全细节是具有挑战性的部分。因此,我建议在OpenID这种设计良好的系统的基础上进行构建。

3
在跨域Ajax中,您可能会发现跨域请求的cookie和session丢失。如果您要从您的网站example.com向您的子域名s2.example.com进行ajax调用,则需要在PHP的头部使用属性:
header('Access-Control-Allow-Origin: https://example.com');
header('Access-Control-Allow-Credentials: true');

在 JavaScript 中,您需要添加以下内容:
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!');
        }
    }
}) 

2
您需要这样设置会话cookie域:
session_set_cookie_params($lifetime,$path,'.site.com')

这只有在网站拥有相同的域名称(包括顶级域名 TLD)时才能生效。

更多信息请参见此处

或者,如果您想跨域访问会话,例如从site1.netsite2.com,那么这是不可能实现的。


这是正确的答案。这段代码来自php核心。更多细节可以在这个stackoverflow链接中找到。session_set_cookie_params([ 'lifetime' => $currentCookieParams["lifetime"], 'path' => '/', 'domain' => $cookie_domain, 'secure' => "1", 'httponly' => "1", 'samesite' => 'None', ]); - Richard Tyler Miles

0

如果您的网站可以依赖JavaScript来运行,那么您可以尝试以下操作:

假设您在blammo.com上有一个会话,并且您想从rockblammo.com访问它。在rockblammo.com页面上,您可以加载一个<script>,从blammo.com/get-session.js获取会话ID(从服务器端返回)。一旦返回,您就可以在页面中插入一个新的<script>标签,指向rockblammo.com/set-session.js?sessionId=XXX,其中XXX是刚刚从blammo.com获取的会话ID。现在,在rockblammo.com的服务器端,会话cookie已更新并设置为此会话ID。从现在开始,这两个页面将共享相同的会话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来完成相同的操作。


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