[关闭]
@greensea 2015-01-04T03:16:10.000000Z 字数 3289 阅读 1708

游侠跨域登录说明


所谓跨域登录指的是,在 youxiamotors.com 域名下登录一次之后,就可以自动在 weiting.fm 等游侠其他域名下登录。

具体来说,当用户在 youxiamotors.com 登录之后,再访问 weiting.fm,就会发现自己已经登录到 weiting.fm 了。本文档将对这一过程的实现进行说明。


跨域 cookies 和跨域 AJAX 访问

为了实现跨域登录,我们需要实现跨域 Cookies 和 AJAX 请求。在默认情况下,Cookies 和 AJAX 是不能跨域访问的。为了实现跨域访问,HTTP 规范中引入了两个 HTTP 头,分别是 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials。

跨域 AJAX: Access-Control-Allow-Origin

在发起 AJAX 请求时,如果服务器返回的 HTTP 头中包含了 Access-Ctontrol-Allow-Origin 字段,且值为当前发起 AJAX 请求的域名,那么我们就能够进行跨域 AJAX 访问。

举例来说,我们在 https://weiting.fm 网址下,用 AJAX 请求一个 位于 https://youxiamotors.com 下的 URL,默认情况下是不能成功获取响应的。但如果我们在 youxiamotors.com 侧返回这样一个 HTTP 头:

  1. Access-Control-Allow-Origin: https://weiting.fm

那么我们就能成功获取 youxiamotors.com 的响应了。

跨域 Cookies: Access-Control-Allow-Credentials

使用 Access-Control-Allow-Origin 头虽然可以成功完成 AJAX 请求,但是 AJAX 在发送请求的时候,默认是不会带上客户端的 Cookies 的。而用户身份信息是保存在 Cookies 中的,如果不发送 Cookies,服务器就无法判断用户是否已经登录了。为了让浏览器在跨域请求时也发送 Cookies,我们需要明确地要求浏览器发送 Cookies。

在发送 AJAX 请求的时候,我们需要使用 withCredentials 参数。在 jQuery 中,具体做法如下:

  1. $.ajax({
  2. xhrFields: {
  3. withCredentials: true
  4. },
  5. /** 其他参数... **/
  6. })

这样浏览器在进行跨域请求的时候,就会发送目标域名下的 Cookies。

仅仅告诉浏览器发送用户 Cookies 还不够,我们还需要在服务器上使用 Access-Control-Allow-Credentials 头,用来明确地告诉浏览器可以使用服务器返回的(包含机密内容的)信息。
服务器端需要输出下面的 HTTP 头:

  1. Access-Control-Allow-Credentials: true

具体流程

目前我们已经拥有了进行跨域登录所需的全部知识,现在开始实现游侠的跨域登录。

首先,用户需要在 youxiamotors.com 域名下进行登录,登录成功后,youxiamotors.com 会给用户返回一个 Cookies,用以记录用户登录信息。如果客户端能够提供这个 Cookies,我们就认为这个客户端是已经登录的用户。

此时,用户仅仅在 youxiamotors.com 域名下是已登录状态,在 weiting.fm 域名下仍然是未登录状态。

当用户访问 weiting.fm 时,如果用户未登录,则页面会发起一个 AJAX 请求,目标页面是 youxiamotors.com/user/login/ajax/corstoken.html。该页面会检查用户是否已经在 youxiamotors.com 登录了,如果已登录,则返回一个 token。

token 中包含当前已登录用户的编号,token 有效期和签名。

紧接着,weiting.fm 的页面将这个 token 发送到 weiting.fm 下的一个页面,该页面会检查 token 是否有效(是否过期、是否被使用过、签名是否正确),如果 token 有效,就到数据库中查找对应编号的用户,并将此用户设为已登录(给客户端返回一个身份 Cookies,表明当前客户端已登录)。

weiting.fm 的页面在发现登录成功后,修改页面上响应的信息(比如,将用户名显示在网页上等)。至此,一次跨域登录就完成了。


token 详情

上文中提到的登录 token,是实现跨域登录的关键,其中包含三个信息:


安全分析

重放攻击

为了防止重放攻击,服务端会记录每一个 token 的使用情况。当一个 token 被使用过后,就将这个 token 标记为“已使用”。具体的代码实现是:

假设 token 为 AAA,有效期为 360 秒,那么当这个 token 被使用过之后,我们在 redis 中增加一个名为 token_used_AAA 的 KEY,有效期为 360 秒。

在使用一个 token 前,应该检查这个 token 是否已经被使用过——只要到 redis 中查询即可。

伪造 token

由于每一个 token 都有一个签名,故伪造 token 是不可能的。

我们使用 HMAC-SHA256 对 token 进行签名。服务器上保存这一个 sign_ken,作为签名密钥。

非法获取 token

如果攻击者想要获取 token,则必须获取 youxiamotors.com 下的用户 Cookies。在默认情况下,浏览器会对跨站 Cookies 进行保护,攻击者无法获取用户 Cookies,故无法获取登录 token。


CORS Helper 使用说明

目前的代码中实现了一个 CORS Helper,用于实现跨域登录。

该助手的设计思想是,提供生成 token 和校验 token 的方法,用户可以在 token 中添加自己希望传输给另一个域名的数据(如已登录的用户编号)。

下面描述一个具体的实现,用户已登录 youxiamotors.com,未登录 weiting.fm。现在用户访问了 weiting.fm 页面,我们需要完成一次自动跨域登录。

  1. weiting.fm 页面向 youxiamotors.com 请求一个登录 token,代码片段如下:
  1. $.ajax({
  2. xhrFields: {
  3. withCredentials: true
  4. },
  5. /** 其他参数 **/
  6. });
  1. youxiamotors.com 向 weiting.fm 返回一个登录 token,并附上跨域请求需要的 HTTP 头。返回的内容类似:
  1. Access-Control-Allow-Credentials:true
  2. Access-Control-Allow-Origin:https://weiting.fm
  3. /** 其他 HTTP 头和内容 **/

服务端的代码类似:

  1. $cors = importHelper('CORS');
  2. $token = $cors->encode($_SESSION['user']['user_id']); /// 生成一个 token,并附带已登录用户的 user_id 数据
  3. $cors->allowOrigin(); /// 输出 Access-Control-Allow-* 头
  4. echo json_encode(array('token' => $token));
  1. weiting.fm 将得到的登录 token 使用普通的 AJAX 请求发送到 weiting.fm/corslogin.php 页面

  2. corslogin.php 页面检查 token 是否合法,若合法,则从中提取用户编号,将该用户设为已登陆。服务端代码类似:

  1. $cors = importHelper('CORS');
  2. $user_id = $cors->decode($_POST['token']); /// 检查 token 是否有效,并从中提取出携带的数据
  3. if ($user_id) {
  4. $cors->mark($_POST['token']); /// 将这个 token 标记为“已使用”
  5. /// 代码:将编号为 $user_id 的用户设置为已登录
  6. }
  7. else {
  8. /// token 不合法(过期、签名错误等)
  9. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注