前端跨域问题的解决方案
文章目录
1. 造成跨域的原因
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源.这里的跨域是广义的.
广义的跨域包括:
- 资源跳转: 链接,重定向,表单提交
- 资源嵌入:
<link>,<script>,<img>,<iframe>
等 DOM 标签 - 脚本请求: javascript 发起的 Ajax 请求等
而我们常说的跨域是狭义的,是由浏览器同源策略引起的一类请求场景.
The same-origin policy is a critical security mechanism that restricts how a document or script loaded from one origin can interact with a resource from another origin. It helps isolate potentially malicious documents, reducing possible attack vectors.
来自 MDN
如果两个域名的协议,域名,端口都相同,那我们就说这两个域名是同源的.
2. 同源策略限制些什么?
- 不能向工作在不同源的服务请求数据,即不能发送 Ajax 请求;
- 无法获取不同源的 document / cookies 等 BOM 和 DOM,可以说任何有关另一个源的信息都无法得到.
3. 为什么会有同源策略
3.1 为什么要限制不同源发送请求
假设两个页面,a 页面和 b 页面.如果没有任何限制,b 页面可以向 a 页面请求任何信息,那如果 a 页面是个
银行之类的页面,那就可以进行转账之类的请求.
那既然如此,为什么不限制写,只限制读?
因为如果连请求都发送不出去,那就不能做跨域资源共享了.
3.2 为什么限制跨域的 DOM 读取?
如果不加以限制,很容易通过 iframe 伪装其网站.进而可以获取用户的登录信息等.
4. 跨域的解决方式
4.1 CORS
服务器设置:
ACCESS-CONTROL-ALLOW-ORIGIN: *
只要浏览器检测到响应头带上了 CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。
CORS 分为"简单请求"以及"预检请求":
-
简单请求
使用下列方法之一:- GET
- POST
- HEAD
以及满足 Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。
而且Content-Type
的值仅为下列三者之一:- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
-
预检请求
与上述简单请求不一样."需预检的请求"要求必须首先使用OPTIONS
方法发起一个预检请求到服务器,
以获知服务器是否允许该实际请求。"预检请求"的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
当满足以下任一条件时,即应首先发送预检请求:- 非简单请求的方法
- 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。
- 非简单请求的
Content-Type
- 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。
- 请求中使用了ReadableStream对象。
另外,还有附带身份验证的请求,也就是携带 cookies.
这个需要前端在请求实例中设置:
// 将 XMLHttpRequest 的 withCredentials 标志设置为 true
const xml = new XMLHttpRequest();
// ...
xml.withCredentials = true;
这时,后端需要作出相应的设置.
来自MDN
4.2 JSONP
JSONP 能够跨域的原理就是:动态创建 script 标签,利用 script 标签的 src 请求没有跨域的限制.
代码示例
function updateList (data) {
console.log(data);
}
let tag = document.createElement("script")
tag.src = "http://otherdomain.com/request?callback=updateList";
document.head.appendChild(tag);
代码定义一个全局函数,然后把这个函数名添加到 script 标签中 src 属性的参数 callback 中,
script 的 src 就是需要跨域的请求.服务端收到请求之后,将数据放入 callback 属性的属性值中:
updateList("somedata")
,然后返回给客户端:
// script 响应返回的js内容为
updateList([{
name: 'hello'
}]);
也就是客户端执行 传递的方法 updateList
,函数的参数即是本次跨域请求返回的数据.