跨域资源共享 CORS 与 代理 Proxy

466 阅读5分钟

浏览器的同源策略

浏览器的同源策略(Same-Origin Policy),针对的是使用 XMLHttpRequest 技术的 ajax 请求(普通的嵌入链接、资源引入是不受影响的)。

简单理解,当我正在访问链接 A,如果当前页面发起对另一链接 B 的异步访问,浏览器会检查 B 链接是否和当前网页的链接 A 同域,有任何不同都会被认为是跨域。

  • 相同的协议(https 或 http)
  • 相同的域名(完整的域名)
  • 相同的端口

跨域一般在开发阶段被发现,前端web应用的域(比如 localhost:8080)与所请求的api域(比如 test-api.xxx.com),会触发同源策略,不让访问。

这里主要介绍两个解决跨域的方案。

方案一:跨域资源共享 CORS

操作步骤

  1. 前端需要设置 XMLHttpRequest对象的 withCredentials 属性为 true
  2. 后端设置 CORS,对预检请求,主要有如下头信息

服务器对预检请求,返回的几个重要的头信息:

  1. Access-Control-Allow-Origin:允许哪些域(协议、域名、端口),* 代表不限制
  2. Access-Control-Allow-Headers:允许的头
  3. Access-Control-Allow-Credentials:true | false,允许携带凭证(cookie、HTTP认证信息、客户端SSL证明)
  4. Access-Control-Allow-Methods:允许哪些提交方法,逗号分隔,客户端
  5. Access-Control-Max-Age:允许被缓存多少秒

开发环境,Access-Control-Allow-Origin 可以直接设置为 *,但生产环境还是应该设置具体的域;Access-Control-Allow-Headers 也是一个需要主动设置的头,否则不能从头携带特定的 token。

认证信息的传递

api 接口登录成功,返回 token,客户端将 token 记录(比如存储在 cookie 或者 storage);在发起请求时,主动携带 token(放在头信息上);退出接口时,主动清除该 token。

原理

浏览器的 CORS,就是为了本域上发起的异步请求,不要随随便便的访问一个新域,除非对方允许。(对方域只允许指定的一些域能访问我的资源)。

CORS允许服务器声明哪些来源的网站有权限访问其资源,以及哪些HTTP方法(如GET、POST)和头部信息可以被使用。这种机制可以防止恶意网站获取其他网站的敏感数据,同时仍然允许合法的应用程序进行跨域数据访问。

实际运行的过程,对非简单请求,浏览器会先发送一个预检请求(options 类型的请求)到目标服务器,服务器会返回一些头信息,浏览器检查通过,才会发送真正的请求。如果浏览器的预检请求检查不通过,则会直接返回403给调用者,不会发出真正的请求。

CORS 是浏览器的安全策略,只在浏览器环境中生效,在非浏览器环境的 HTTP 请求是不生效的。

CORS 中的凭证

默认情况下,浏览器发出的跨域请求是不会携带凭证的(cookie、HTTP认证信息、客户端SSL证明),(同源策略)。如果希望在跨域请求中携带凭证,需要设置 XMLHttpRequest对象的withCredentials 属性为 true,这会告诉浏览器在请求中包含凭据信息,如 Cookies

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'https://example.com/api/data', true);
xhr.send();

如果预检请求中已经得知服务器不接受凭证(Access-Control-Allow-Credentials: false 或者 没这个头),但此时还设置 withCredentials 属性为 true,浏览器会阻止这个请求。如果简单请求(也就是不会触发预检)携带凭证,但服务器不允许携带凭证,服务器的请求会正常返回,但浏览器会拦截掉响应,替代为一个 size=0 的响应,并抛出未通过跨域的异常。

方案二:代理 Proxy

操作步骤

以 nginx server 为例

location ^~ /api/ {
    rewrite ^/api/(.*) /$1 break;
    proxy_pass http://api.test.com; # 被代理目标
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_cookie_domain ~^\.?.* $host; # 修改 cookie 的 domain
}

以前端项目的 proxy 代理为例

proxy: {
    "/api": {
        target: env.domain,
        // pathRewrite: {
        //     "^/api": "/",
        // },
        cookieDomainRewrite""// 抹掉cookie domain
    },
},

原理

代理的模式,可以理解为浏览器请求同域的服务器,服务器去请求目标API服务器,拿到结果后,直接返回给客户端浏览器。这个代理行为不在浏览器里,自然不会被浏览器的同源策略限制。

注意1:被代理的目标,依然要允许客户端的域名

原理是这样的,虽然代理服务器不是浏览器,不会有同源策略去检查能否发起跨域请求,但目标服务器,还是会检查该请求是否是跨域请求请求头中的的 Origin 是否在允许跨域的列表中。

有的地方会说,代理服务器使用 changeOrigin,这完全不会生效。这里的 changeOrigin,按照文档的描述,是改头信息中 Host 里面的 origin 部分。你可以理解成为设置了这个,代理才会将请求地址改成目标服务器地址,新版本的 proxy 的这个 changeOrigin,默认就是true。

注意2:代理服务器要修改cookie的一些信息

代理服务器要修改cookie的一些信息,比如 domain、path,否则不能正常设置到客户端的浏览器。如上文 nginx 的 proxy_cookie_domain 或者 js 的 cookieDomainRewrite