一、什么是跨域?
跨域问题的来源是浏览器为了请求安全而引入的基于同源策略的安全特性。当网页和请求的协议、域名或者端口号不同时,浏览器则判定两者为非同源,即为跨域请求。
当违背同源策略时,会有以下限制:
- 无法获取非同源网页下的cookie、LocalStorage、SessionStorage等内容
- 无法获取非同源的dom
- 无法向非同源的网页发送ajax请求
需要注意的是,跨域问题只是浏览器的限制而非服务端的限制,实际请求已经发出并响应了,只是被浏览器隐藏了返回内容。
二、跨域的解决方式
常见的解决方案主要有CORS、反向代理和JSONP
2.1 CORS(Cross Origin Resource Sharing)
CORS的跨域解决主要是依赖于浏览器和服务端通过一些HTTP协议头来做一些约定和限制。
和CORS相关的协议头
| 请求头 | 说明 |
|---|---|
| Origin | 表明预检请求或者实际请求发出的源站URL,不管是否跨域Origin字段总会被发送 |
| Access-Control-Request-Method | 表明当前请求的HTTP方法 |
| Access-Control-Request-Headers | 告知服务器当前请求携带的首部字段,例如 content-type |
| 响应头 | 说明 |
|---|---|
| Access-Control-Allow-Origin | 指定允许访问该资源的外域URL,对于携带身份凭证的请求不可使用通配符* |
| Access-Control-Allow-Credentials | 是否允许浏览器读取返回内容;用在预检请求的响应中指定实际的请求是否可使用 credentials 字段 |
| Access-Control-Allow-Methods | 指定请求所允许使用的HTTP方法 |
| Access-Control-Allow-Headers | 指定请求所允许携带的首部字段 |
CORS跨域处理
CORS把请求分为简单请求和需预检请求,两者处理跨域的方式不同。如果需要跨域传递凭证信息(可简单理解为cookie),则还需要特殊处理。
简单请求
当满足以下条件时即为简单请求:
- 请求方法:
GET、HEAD、POST。 - 请求头:
Accept、Accept-Language、Content-Language、Content-Type。- Content-Type 仅支持:
application/x-www-form-urlencoded、multipart/form-data、text/plain。
- Content-Type 仅支持:
对于简单请求,服务端可以通过设置Access-Control-Allow-Origin字段为
通配符 *,表示此资源可以被所有域名共享,但前提是不带凭证信息。具体的URL 如 http://localhost:8000,指定允许访问该资源的所有跨域URL。
浏览器通过比对当前页面URL和该字段设置的URL判断是否隐藏返回内容。
需预检请求
非简单请求即为需预检请求,需预检请求在正式发出前都要先发送一个预检请求。
预检请求以OPTIONS方法发送,携带相关字段例如Access-Control-Request-Method、Access-Control-Request-Headers。
而浏览器则通过预检请求的返回响应头字段例如Access-Control-Allow-Methods、Access-Control-Allow-Headers,当请求符合响应头的这些条件时才继续发送正式请求。
带凭证信息的请求
还有一种情况我们经常遇到。浏览器在发送请求时需要给服务端发送 cookie,服务端根据 cookie 中的信息做一些身份验证等。
默认情况下,浏览器向不同域发送 ajax 请求时,不会携带发送 cookie 信息。
如果要让浏览器发送 cookie,则需要在客户端设置 XMLHttpRequest 属性 withCredentials: true,表示当前请求将携带凭证信息。
同时,服务端需要设置响应头 Access-Control-Allow-Credentials: true和Access-Control-Allow-Origin: 当前URL(不能指定为通配符 *),表示服务端允许接受来自当前URL的请求携带凭证信息。
此处需要注意的是,cookie 依然遵循同源策略,即使使用了withCredentials: true,携带的也是对应跨域服务器所设置的 cookie ,其他域名的 cookie 不会上传。
HTML的 crossorgin 属性
一些元素允许跨域嵌入,例如<img>和<script>,但同源策略保护跨域读取,不允许JavaScript读取其内部信息。声明 crossorgin 属性可为元素启用 CORS 从而解除相关限制。
各元素在未开启 CORS 时的限制
<img>:在canvas中使用drawImage()绘制图像时,若传入未声明crossorigin属性的跨源<img>元素,会使canvas变成污染状态(tainted),此时任何读取canvas数据的操作,如getImageData()、toDataURL()、toBlob()等,均会抛出错误。<audio>和<video>:任何可能暴露内容信息的操作都需要设置crossorigin属性,如Web Audio API、在<canvas>或WebGL中使用<audio>或<video>元素等。此外,根据规范,用于嵌入字幕的<track>元素的跨源状态继承自其父元素<audio>或<video>的crossorigin属性。<script>:对于跨源的传统脚本(未声明type=module的脚本),若未声明crossorigin属性,在发生错误时,window.onerror中不会收到详细的错误信息,仅有类似“Script error”这样的简单提示。<link>:若未声明crossorigin属性,JavaScript不能访问使用该元素导入的跨源样式表等资源,如document.styleSheets中相应样式表的cssRules、rules属性及insertRule()、deleteRule()等方法均不能访问,否则会抛出错误。
crossorgin 的可选值
anonymous或其他不合法字符串:让浏览器启用CORS访问检查,检查http响应头的Access-Control-Allow-Origin,凭据模式设为same-origin只有同源下才发送cookies等用户凭据。use-credentials:让浏览器启用CORS访问检查,且任何时候都发送cookies等用户凭据。
2.2 反向代理
反向代理解决跨域的原理是 服务端不存在同源限制,通过给页面同源下配置一套服务器去接收非同源服务器响应的数据,再转发给页面,从而将非同源请求转化为同源请求。
通常使用Nginx配置服务器,反向代理的相关配置项:
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}
2.3 JSONP
JSONP解决跨域的原理是依赖浏览器加载资源文件(例如<img>和<script>)时不受同源策略的限制。缺点是只支持 get 请求。
具体流程:
- 全局注册一个函数,例如:
window.getHZFEMember = (num) => console.log('HZFE Member: ' + num);。 - 构造一个请求 URL,例如:
https://hzfe.org/api/hzfeMember?callback=getHZFEMember。 - 生成一个
<script>并把src设为上一步的请求 URL 并插入到文档中,如<script src="https://hzfe.org/api/hzfeMember?callback=getHZFEMember" />。 - 服务端构造一个 JavaScript 函数调用表达式并返回,例如:
getHZFEMember(17)。 - 浏览器加载并执行以上代码,输出
HZFE Member: 17。