什么是跨域
浏览器作为请求方与被请求方的域名、协议、端口三者中有一个不同即被称作跨域。
跨域会导致什么问题
- ajax 请求被浏览器拦截
- cookie、Storage、indexDB 等缓存不互通
那么为什么会有跨域呢?那要从同源说起。
什么是同源策略(same origin policy)
两个 url 的域名、协议、端口都相同,称为两个 url 同源。 同源是浏览器的安全限制,默认只有同源的两个网站才能互相获取数据。 如果没有同源限制,不同网站之间可以共享 cookie,攻击者可以直接获取 cookie 发起 CSRF(跨站请求伪造)攻击;接口随便调用,导致数据泄漏或被删。
跨域的解决方案
跨域的解决方案有:script 等标签、JSONP、iframe、node 请求转发、CORS、nginx 反向代理等
img、link、script 标签
img、link、script 标签通过 src 或 href 属性支持访问跨域资源,通常为 cdn 资源、第三方包资源。
<img src="xxx" />
<link href="xxx" />
<script src="xxx"></script>
jsonp
jsonp 算是 script 标签跨域的一种变体,通过在 script 标签的 src 属性后面加上 callback 字段实现跨域请求数据。
- 客户端
<script>
function getMenuData(data) {
// 根据data渲染菜单
}
</script>
<script src="http://www.a.com/data?callback=getMenuData"></script>
- 服务端
MenuData({ status: "success", data: ["menu1", "menu2"] });
客户端和服务端在开发阶段做好数据约定,由于它仅支持 get 请求,现在使用场景已经很少了。
iframe
iframe 的 postMessage 方法 天然支持跨域通信,iframe 内外的页面可以采用不同源的 url。
- 父页面 www.a.com
<iframe src="www.b.com"
><iframe>
<script>
window.addEventListener("message", function (event) {
console.log(event.data);
});
</script></iframe
></iframe
>
- 子页面 www.b.com
window.postMessage(message, "http://www.a.com");
nodejs 请求转发
使用 nodejs 创建一个 中转 server, 请求通过访问 node server 转发到目标地址, 突破跨域限制。 从一开始的:
A => C A请求C
A <= C C响应A
转变为:
A => B => C A请求B,B请求C
A <= B <= C C响应B,B响应A
var koa = require("koa");
var proxy = require("koa-proxy");
var app = koa();
app.use(
proxy({
host: "http://www.a.com", // 最终访问的api地址
})
);
app.listen(3000);
tips:同源策略是浏览器的限制,不是服务器的限制。 此方法常用于开发时,后端不提供跨域支持,由前端解决跨域限制。现在 webpack-dev-server 自带 proxy,不再需要自己启动额外的 server,只用添加配置就行,如下:
'/api': {
target: 'https://http://123.123.123.1:8080',
changeOrigin: true,
secure: false,
pathRewrite: {
'^/api': '/api'
}
}
cors
cors 是服务端配置,配置跨域资源请求之后,服务端资源可被跨域请求。主要为以下几个响应头:
- Access-Control-Allow-Origin 允许访问的域名
- Access-Control-Allow-Methods 允许请求的方法
- Access-Control-Allow-Headers 允许请求的 header
- Access-Control-Allow-Credentials 是否允许携带 cookie
值得注意得是,服务端设置了 CORS 之后需要针对 options 方法的请求做单独处理。
什么是 options 请求
options 请求是浏览器主动泛起的一个刺探请求。 发送简单请求时浏览器不会生成刺探请求,发送复杂请求时会先发送一个嗅探请求,请求方法为 options,待刺探请求成功返回后才发送真正的请求。 嗅探请求的作用是:提前将实际请求所使用的方法和会携带的首部字段发送给服务器,防止对服务器数据产生副作用。 options 请求产生的请求头
origin: http://www.client.com
Access-Control-Allow-Methods: put...
Access-Control-Allow-Headers: Content-type...
-
简单请求 满足以下条件的请求:
-
请求方法为 HEAD、GET、POST 中的一种,
-
请求头不超过以下几个:
-
- Accept
- Accept-Language
- Content-Language
- Content-Type:application/x-www-form-urlencoded || multipart/form-data || text/plain
- DPR
- Downlink
- save-Data
- ViewPort-Width
- Width
- 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器
- 请求中没有使用 ReadableStream 对象。
- 复杂请求 以上简单请求之外的请求
nginx 反向代理
nginx 反向代理是指客户端访问服务器路径 /api 地址时,服务器将 路径/api 下的请求都代理到指定的服务下面,原理和借用 nodejs 实现请求转发是一样的。
location /api/ {
proxy_pass http://123.123.123.1:8080/api/;
index index.html index.htm;
}
tips
跨域和同源其实不难,主要是要能结合理论解决日常遇到的问题:比如
-
Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource 你能很快定位问题是服务端没有设置跨域导致的。
-
Mixed content: the page at 'www.xxx.com/' was loaded over HTTPS, but resquested an insecure prefetch resource 'www.ooo.com/'. This content should also be served over HTTPS 你能很快理解这是浏览器的安全限制。
-
是不是所有复杂请求都会发起 options 刺探请求? 不是的,options 请求可以设置缓存时间,在过期之前不会再发刺探请求
Access-Control-Max-Age: time
关于浏览器安全限制、iframe父子页面间通信还有很多内容,欢迎评论、点赞。
此文章为 【前端基础】 系列文章,关注小姐姐,一起学一学!