ThinkJS
首发于ThinkJS

ThinkJS 中如何解决跨域

引言

同源策略 (Same origin policy)是一种约定,它限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。本文介绍两种常用跨域解决方案 JSONP 和 CORS,以及在 ThinkJS 中解决跨域的方法。

怎样才是同源

如果两个页面的协议、端口和域名都相同,则两个页面具有相同的源。下表给出了相对http://store.company.com/dir/page.html 同源检测的示例:

同源检测表,数据来自mdn

非同源下发起的请求叫做跨域请求,以 chrome 为例,如果服务端不支持跨域请求,会出现如下错误:

如果不在服务端允许的域名列表中,则会出现如下错误:

两种跨域解决方案

JSONP 的原理

为了理解这种模式的原理,先准备了一个返回以下 JSON 数据的 API。

{
    "errno":0,
    "errmsg":"",
    "data":[{ "id":1, "name":"池子" },{ "id":2,"name":"李诞" }]
}

如果把 <script> 元素的 src 属性设成这个 API,可以得到 JSON 数据,然而 JavaScript 引擎无法解析 JSON 数据,因此 API 必须返回可执行的 JavaScript。在使用 JSONP 时,我们先全局添加一个回调函数 jsonpCallback,浏览器发请求时把添加的回调函数的名称当作请求中查询参数的一部分。

<script type="text/javascript"
         src="http://localhost:8360/index/users?jsonp=jsonpCallback">
</script>

服务器会在传给浏览器前将 JSON 数据填充到回调函数 jsonpCallback 中,此时浏览器得到的是 JavaScript 脚本,示例如下:

jsonpCallback({
    "errno":0,
    "errmsg":"",
    "data":[{ "id":1, "name":"池子" },{ "id":2,"name":"李诞" }]
})

CORS 的原理

CORS 通过在请求头和响应头部添加字段,服务端通过请求头部判断该域名是否可以和本服务器进行跨域通信,如果可以进行跨域通信,在之后的通信过程中请求都会带着 Origin 头信息字段,服务器的响应也都会有一个 Access-Control-Allow-Origin 头信息字段。

CORS 需要浏览器和服务端同时支持,整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

详细了解请参考阮一峰老师的博客 跨域资源共享 CORS 详解 - 阮一峰的网络日志

比较 JSONP 和 CORS

JSONP 只支持GET请求获取数据,CORS 可以通过配置`Access-Control-Allow-Methods`支持多种请求方法。

JSONP 有安全问题,CORS 更安全。

粗略的 JSONP 部署很容易受到跨站请求伪造(CSRF/XSRF)的攻击。因为HTML <script> 标签在浏览器里不遵守同源策略,恶意网页可以要求并获取属于其他网站的 JSON 数据。当用户正登录那个其他网站时,上述状况使得该恶意网站得以在恶意网站的环境下操作该 JSON 数据,可能泄漏用户的密码或是其他敏感数据。

只有在该 JSON 数据含有不该泄漏给第三方的隐密数据,且服务器仅靠浏览器的同源策略阻挡不正常要求的时候这才会是问题。若服务器自己决定要求的专有性,并只在要求正常的情况下输出数据则没有问题。只靠 Cookie 并不够决定要求是合法的,这很容易受到跨站请求伪造攻击。

Via: zh.wikipedia.org/wiki/J

JSONP 支持度更好,CORS 低版本浏览器不支持,因此可以考虑在低版本浏览器上用 JSONP,高版本浏览器用 CORS。

使用 ThinkJS 解决跨域

使用 JSONP

在 src/config/config.js 中通过 jsonpCallbackField 指定从查询参数中哪个字段获取回调函数名。

// default config
module.exports = {
  workers: 1,
  jsonpCallbackField: 'jsonp'
};

在控制器中响应 JSONP 请求

const Base = require('../base');

module.exports = class extends Base {
  async test2Action() {
    const users = [{
      id: 1,
      name: '池子'
    }, {
      id: 2,
      name: '李诞'
    }];
    // 判断是否 JSONP 请求
    if (this.isJsonp()) {
      return this.jsonp(users); // 响应体 MIME 类型为 application/javascript
    }
    return this.success(users); // 响应体 MIME 类型为 application/json
  }
}

使用 CORS

ThinkJS 3 基于 Koa 开发,有些 Koa 的中间件在 ThinkJS 3 中可以直接使用,详细请看 thinkjs/think-awesome

首先安装 @koa/cors 中间件,在 src/config/middleware.js 中引入并添加配置,代码如下:

const cors = require('@koa/cors');
module.exports = [
  {
    handle: 'meta',
    options: {
      logRequest: isDev,
      sendResponseTime: isDev
    }
  },
  {
    handle: cors
  },
  // ...
]

这样就是最简单的 CORS 配置了,配置文档请看 koajs/cors 。配置好后再发跨域请求如下图所示。

最后

欢迎使用 ThinkJS 的开发着积极投稿,有什么骚操作可以秀给大家看。

编辑于 2018-05-30

文章被以下专栏收录

    ThinkJS 是一款基于 Koa 2.0 面向未来的企业级 Node.js 框架,致力于整合了大量的项目最佳实践,让企业级开发变得如此简单、高效。本专栏会分享一些在 ThinkJS 项目开发过程中总结的一些经验以及问题,同时也非常欢迎大家投稿。