浏览器的同源策略
浏览器的同源策略(Same-Origin Policy),针对的是使用 XMLHttpRequest 技术的 ajax 请求(普通的嵌入链接、资源引入是不受影响的)。
简单理解,当我正在访问链接 A,如果当前页面发起对另一链接 B 的异步访问,浏览器会检查 B 链接是否和当前网页的链接 A 同域,有任何不同都会被认为是跨域。
- 相同的协议(https 或 http)
- 相同的域名(完整的域名)
- 相同的端口
跨域一般在开发阶段被发现,前端web应用的域(比如 localhost:8080)与所请求的api域(比如 test-api.xxx.com),会触发同源策略,不让访问。
这里主要介绍两个解决跨域的方案。
方案一:跨域资源共享 CORS
操作步骤
- 前端需要设置 XMLHttpRequest对象的
withCredentials属性为true - 后端设置 CORS,对预检请求,主要有如下头信息
服务器对预检请求,返回的几个重要的头信息:
- Access-Control-Allow-Origin:允许哪些域(协议、域名、端口),* 代表不限制
- Access-Control-Allow-Headers:允许的头
- Access-Control-Allow-Credentials:true | false,允许携带凭证(cookie、HTTP认证信息、客户端SSL证明)
- Access-Control-Allow-Methods:允许哪些提交方法,逗号分隔,客户端
- 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