基础 03 谈谈前端跨源问题及解决方法
所谓跨源,是指一个域下的文档或脚本试图去请求另一个域下的资源。
广义的跨源:
资源跳转:
a
标签、重定向、表单提交;资源嵌入:
<link>
、<script>
、<img>
、<frame>
等标签,background:url()
、@font-face()
等文件外链;脚本请求:Js 发起的 Ajax 请求、
Dom
和Js
对象的跨源操作(如iframe
)等;
狭义的跨源:也就是我们常说的跨源问题,是指由浏览器同源策略限制的一类请求场景。典型的是 Ajax 跨源请求。
同源和同站
域名级数是指一个域名由多少级组成,域名的各个级别被 .
分开,简而言之,有多少个点就是几级域名。
一般来说就是比如 www.baidu.com
:
顶级域名: .com;
一级域名(也有称顶级域名): baidu .com;
二级域名: www.baidu .com、aaa.baidu .com、bbb.baidu .com 二级域名;
同源的定义
两个 URL 的协议、域名、端口都相同的话,则这两个 URL 是同源。
同源的例子:
http://example.com/app1/index.html http://example.com/app2/index.html | 同源。协议、域名都相同 |
http://example.com:80 http://example.com | 同源。协议、域名都相同,服务器默认的http端口是80 |
不同源的例子:
http://example.com/app1 https://example.com/app2 | 不同源。协议不同 |
http://example.com http://www.example.com http://myapp.example.com | 不同源。域名不同 |
http://example.com http://example.com:8080 | 不同源。端口不同 |
注意: IE 浏览器中两个相互之间高度互信的域名,如公司域名,不受同源策略限制。
注意: IE 浏览器未将端口号纳入到同源策略的检查中。因此,同协议、同域名,但是端口不同的链接不受同源策略限制。如:
同站的定义
同站的判断宽松:只要两个 URL 的 eTLD+1 相同即可,不需要考虑协议和端口。
其中,eTLD 表示有效顶级域名,注册于 Mozilla 维护的公共后缀列表(Public Suffix List)中,例如,.com、.co、.uk、.github.io
等。而 eTLD+1 则表示,有效顶级域名+二级域名,例如 taobao.com
等。
比如:www.taobao.com
和 www.baidu.com
是跨站,www.a.taobao.com
和 www.b.taobao.com
是同站,a.github.io
和b.github.io
是跨站。
同源策略作为浏览器的安全基石,其同源判断是比较严格的,而相对而言,Cookie 中的同站判断就比较宽松。
什么是同源策略?
**同源策略(same-origin policy)**是一个重要的安全策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。它的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。如:CSRF(跨站请求伪造,Cross-site request forgery)攻击。
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。随着互联网的发展,同源政策越来越严格。
如果非同源,共有三种行为受到限制:
Cookie、LocalStorage 和 IndexDB 无法读取;
DOM 无法获得;
Ajax 请求不能发送。
但是以下标签通常是允许跨源加载资源的:
<img src=''>
<video src=''>
<audio src=''>
<link href=''>
<script src=''>
<iframe src=''>
另外,还有一些注意点:
<script src='' />
标签嵌入跨源脚本,语法错误信息无法被当前页面脚本捕捉。@font-face
引入的字体,在不同浏览器有不同的限制。JavaScript API 访问跨源 window 对象时,Window、Location 对象的方法和属性受限制。具体如下:
Window 可跨源访问:window.blur、window.close、window.focus、window.postMessage
Window 只读属性:window.closed、window.frames、window.length、window.opener、window.parent、window.self、window.top、window.window
Window 读/写属性:window.location
Location 可跨源访问:location.replace、location.href = ''(可写,不可读)
常见的跨源场景
Cookie跨源
Cookie 是服务器写入浏览器的一小段信息,只有同站的网页才能共享。
注意:**浏览器本身是允许 cookie 同站通信的。**即,只要两个 URL 的 eTLD+1 相同即可,不需要考虑协议和端口。
两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置 document.domain
共享 Cookie。
如:A 网页是 http://w1.example.com/a.html,B 网页是 http://w2.example.com/b.html,那么只要设置相同的 document.domain
,两个网页就可以共享 Cookie。
另外,服务器在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名+二级域名。这样的话,其子域名不用做任何设置,都可以读取这个 Cookie。
iframe跨源
iframe 是在当前页面嵌入其他页面,可以是同源,也可以是非同源页面。每个 iframe 有自己的窗口,即有自己的 window 对象。iframe 的脚本可以获得父窗口和子窗口。但是,只有在同源的情况下,父窗口和子窗口才能通信;如果跨源,就无法拿到对方的 DOM。
比如,父窗口运行下面的命令,如果 iframe 内嵌的页面非同源,就会报错:
反之亦然,子窗口获取主窗口的 DOM 也会报错。
这种情况不仅出现在 iframe 窗口,还出现在 window.open
方法打开的窗口,只要跨源,父窗口与子窗口之间就无法通信。
如果两个窗口一级域名相同,只是二级域名不同,那么可以上述修改 document.domain
的方法规避同源政策。
LocalStorage跨源
LocalStorage 和 IndexedDB 都是受同源政策限制的,无法跨源访问。
AJAX跨源
浏览器的同源政策规定,AJAX 请求只能发给同源的网址,否则就报错。
1.CORS(跨源资源共享)
CORS,跨源资源共享(Cross-origin resource sharing), 是一个 W3C 标准。它允许浏览器向非同源的服务器,发出 XMLHttpRequest
请求,从而克服了 Ajax 只能同源使用的限制。
这是解决跨源 Ajax 请求的根本方法。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于IE10。
整个 CORS 通信过程都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 Ajax 通信没有差别,代码完全一样。浏览器一旦发现 Ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
CORS 允许在下列场景中使用跨源请求:
由 XMLHttpRequest 或 Fetch 发起的跨源请求;
@font-face 引用的跨源字体资源;
WebGL 贴图;
使用 drawImage 将 Images/video 资源绘制到 canvas。
CORS 实现
客户端无需处理,浏览器会自动在请求头中增加一些字段。 如果要发送跨源 Cookie,请求头要设置 withCredentials
属性。
服务端响应头要设置 Access-Control-Allow-Origin
、Access-Control-Allow-Headers
、Access-Control-Allow-Credentials
属性。
请求头字段
当浏览器发起跨源请求时,会自动添加如下请求头字段。请注意,这些字段无须开发者手动添加。
Origin:表明预检请求或实际请求的源站,值为源站 URI。它不包含任何路径信息,只是服务器名称。
Access-Control-Request-Method:用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。
Access-Control-Request-Headers:用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。
响应头字段
Access-Control-Allow-Origin:origin | *,设置允许跨源的源,origin 指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符
*
,表示允许来自所有域的请求。Access-Control-Expose-Headers:让服务器把允许浏览器请求头中的字段放入白名单。
在跨源访问时,XMLHttpRequest 对象的
getResponseHeader()
方法只能拿到一些最基本的响应头字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
,如果要访问其他字段,则需要服务器设置本响应头。Access-Control-Allow-Credentials:true | false:指定了当浏览器的
credentials
设置为 true 时是否允许浏览器读取 response 的内容。当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用credentials
。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。
withCredentials 属性
CORS 请求默认不包含 Cookie 信息(以及 HTTP 认证信息等)。如果需要包含 Cookie 信息,客户端和服务器都要加配置:
服务端响应头设置
Access-Control-Allow-Credentials: true
;客户端请求头设置
withCredentials = true
。
否则,即使服务器同意发送 Cookie,浏览器也不会发送。或者,服务器要求设置 Cookie,浏览器也不会处理但是,如果省略withCredentials
设置,有的浏览器还是会一起发送 Cookie。这时,可以指定 withCredentials = false
。
需要注意的是:**如果要发送 Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。**同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie
也无法读取服务器域名下的 Cookie。
两种请求
CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
请求方法是这三种方法之一:HEAD、GET、POST
HTTP 的头信息不超出以下几种字段。
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。一句话,简单请求就是简单的 HTTP 方法与简单的 HTTP 头信息的结合。
这样划分的原因是,表单在历史上一直可以跨源发出请求。简单请求就是表单请求,浏览器沿袭了传统的处理方式,不把行为复杂化,否则开发者可能转而使用表单,规避 CORS 的限制。对于非简单请求,浏览器会采用新的处理方式。
简单请求的跨源流程
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个Origin
字段(包含协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果 Origin
指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现这个回应的头信息没有包含 Access-Control-Allow-Origin
字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest
的 onerror
回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是 200。
非简单请求的跨源流程
非简单请求是那种对服务器提出特殊要求的请求,比如请求方法是 PUT
或 DELETE
,或者 Content-Type
字段的类型是 application/json
。
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest
请求,否则就报错。这是为了防止这些新增的请求,对传统的没有 CORS 支持的服务器形成压力,给服务器一个提前拒绝的机会,这样可以防止服务器大量收到 DELETE
和 PUT
请求,这些传统的表单不可能跨源发出的请求。
预检请求的回应:
上述讲到的“预检”请求,用的请求方法是 OPTIONS
,表示这个请求是用来询问的。头信息里面包含:
Origin
,表示请求来自哪个源。Access-Control-Request-Method
,列出浏览器的 CORS 请求会用到哪些 HTTP 方法,上例是PUT
。Access-Control-Request-Headers
,该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段。
服务器收到“预检”请求以后,检查了 Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应。
如果服务器否定了“预检”请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段,或者明确表示请求不符合条件。
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个 Origin
头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin
头信息字段。
2.Nginx反向代理
使用 Nginx 反向代理实现跨源,是最简单的跨源方式。只需要修改 Nginx 的配置即可解决跨源问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。
实现思路: 通过 Nginx 配置一个代理服务器(与发送请求的页面同源)做跳板机,将请求反向代理到真正的 URL,并且可以修改一些头部信息,如:Cookie。
安装: 下载安装包或者 npm i nginx
,运行 nginx -v
查看是否安装成功。
配置: 修改 nginx.conf
文件(Mac:/usr/local/etc/nginx/nginx.conf
):
更新配置: 执行 nginx -s reload
重新载入配置。
了解更多 Nginx,可查看 Nginx基础使用。
3.Node中间件代理
实现原理:同源策略是浏览器需要遵循的标准,而如果是其他服务器(如:node)向服务器请求就无需遵循同源策略。
代理服务器需要做如下事情:
接受客户端请求 ;
将请求转发给服务器;
拿到服务器响应数据;
将响应转发给客户端。
具体实现:http://127.0.0.1:5500/index.html
通过代理服务器提供的 http://localhost:3000
接口,向目标服务器http://localhost:4000
请求数据。
4.webpack-dev-server
Webpack 内置了 webpack-dev-server
插件,webpack-dev-server
使用 http-proxy-middleware
实现跨源代理。
webpack.config.js
配置:
5.WebSocket
原理: 同源政策是浏览器对 http/https
协议的限制,WebSocket 是一种使用 ws://
(非加密)和wss://
(加密)作为前缀的通信协议,浏览器不对该协议实行同源政策。
客户端发送 Websocket 请求:
Node.js 服务器:
了解更多 WebSocket ,可查看 前端进阶篇:WebSocket。
6.JSONP
JSONP 是一种非官方的跨源数据交互协议。它的实现主要依赖于两点:
<script>
标签不受同源策略限制,可以从不同域加载资源;<script>
请求回来的是一段 Js 代码,并且会立即在当前页面执行。
JSONP 的理念:与服务端约定好一个回调函数名,服务端接收到请求后,将返回一段 Javascript,在这段 Javascript 代码中调用了约定好的回调函数,并且将数据作为参数进行传递。当网页接收到这段 Javascript 代码后,就会执行这个回调函数,这时数据已经成功传输到客户端了。
首先,网页动态插入 <script>
元素,由它向跨源网址发出请求。
注意:该请求的查询字符串有一个 callback
参数,用来指定回调函数的名字,这对于 JSONP 是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
由于 <script>
元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 foo
函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用 JSON.parse
的步骤。
**JSONP 的优点是:**简单适用,兼容性更好,在更加古老的浏览器中都可以运行。虽然绝大多数现代的浏览器都已经支持 CORS,但一些比较老的浏览器只支持 JSONP。
**JSONP 的缺点是:**它只支持 GET 请求,而不支持 POST 请求等其他类型的 HTTP 请求。
7.window.postMessage
window.postMessage()
方法提供了一种受控机制来规避此限制,只要正确的使用,就可以安全地实现跨源通信。
从广义上讲,一个窗口可以获得对另一个窗口的 window 对象(如:targetWindow = window.opener
),然后在窗口上调用 targetWindow.postMessage()
方法发送消息,该消息可以被目标窗口的全局事件 message
接收。
targetWindow: 其他窗口的一个引用。通过以下方法获取:
调用
window.open()
返回的窗口对象;页面上的 iframe 对象:比如 iframe 的
contentWindow
属性、、或者是命名过或数值索引的window.frames。
**message:**将要发送到其他 window 的数据。
targetOrigin: 指定哪些窗口能接收该消息,其值可以是字符串
*
,表示无限制,也可以是一个 URI。如果目标窗口的协议、域名、端口这三者的任意一项不匹配targetOrigin
提供的值,那么消息就不会被发送。**transfer ** 可选,是一串和
message
同时传递的Transferable
对象。
以 window.open
为例:
父页面向子页面发送消息:
子页面向父页面发送消息:
注意: 如果不是当前页面调用 window.open()
打开或者 iframe
内嵌的页面,则跟当前页面没有任何关系,即取不到页面窗口的 window 对象,也就无法使用 postMessage
方法发送信息。
注意: 如果 window.open()
打开或者 iframe
内嵌的页面是同源页面,则 postMessage
方法中的第二个参数是不传;如果是非同源页面,则第二个参数必传(可以传 *
或 URI),否则会报错,如:
postMessage+window.open
实现思路: 在页面 A (父窗口)调用 window.open()
打开多个子页面 B、C...(即多个子窗口),并将子页面的 window 对象存在一个数组中。如果页面 A 需要广播消息,只需遍历数组,调用数组中 window 对象的 postMessage
方法发送消息即可;如果子页面 B 需要广播消息,则调用 window.opener.postMessage
方法向页面 A 发送消息,页面 A 再在 message
事件中遍历数组,调用数组中 window 对象的 postMessage
方法发送消息。
首先,在页面 A 把 window.open()
方法打开的页面的 window 对象储存到数组,然后定义 message
事件的监听函数:
然后,在各子页面定义 message
事件的监听函数:
最后,在子页面(如 B)中调用 postMessage
方法,实现广播消息:
注意: 子页面的 window 对象上的 opener
属性,指向的是父页面的 window 对象,因此,子页面获得了父页面的控制权。出于安全考虑,可以限制子页面的 window.opener
属性。如果是 a
标签跳转,可以加 rel=noopener
属性或者 rel=noreferrer
,如果是 Js 调用 window.open()
方法,可以:
注意: 该方案使用有限制,被打开页面必需要有 window.opener
属性,并且指向打开该页面的页面 window 对象。即,如果子页面不是通过在另一个页面内的 window.open
打开的(如直接在地址栏输入链接或者从其他网站链接过来),则两者之间没有联系,无法通信。
postMessage+iframe
该方案与 postMessage+window.open
类似,区别在于:适用于在当前页面内嵌 iframe 子页面的场景。父页面调用类似 window.frames[0].postMessage()
方法向子页面发送信息,子页面在 message
事件中调用 window.parent.postMessage()
或者 e.source.postMessage()
向父页面通信。
实现思路: 非同源页面 A 和 B ,A 内嵌一个 iframe 页面 C1,B 内嵌一个 iframe 页面 C2,C1、C2 指向同一 URL(或同源下的不同 URL 也可以)。当页面 A 需要向 页面 B 通信时,先将消息发给其内嵌的 iframe C1,由于 C1、C2 同源,它们之间可以使用上述讲到的任意方法通信,即 C2 可以接收 C1 发送的消息,然后再由 C2 将发送信息给页面 B。
我们以 BroadCast Channel 为例。
首先,在中间页面创建一个 BroadCast Channel 实例,并为实例的 message
事件上定义函数,接收来自 BroadCast Channel 的消息,再定义一个全局的 message
事件定义函数,接收来自父页面的消息。
然后,在页面 A、B 中定义全局的 message
事件函数,接收来自 iframe 的消息:
最后,在页面 A 或者 B 发出消息:
注意: 主页面调用 iframe 的 postMessage
方法,需要等 iframe 加载完。否则报错:
8.document.domain
满足某些限制条件的情况下(两个源的一级级域和二级域名相同,只是次级域名不同),页面是可以修改它的源。Js 可以将 document.domain
的值设置为其当前域或其当前域的父域,并且这个修改后的域将用于后续源检查。
注意:document.domain只能设置为当前域和其父域,否则,浏览器会拒绝修改。
**注意:**端口是由浏览器另行检查的。任何对 document.domain
的赋值操作,包括 document.domain = document.domain
都会导致端口被重写为 null
。
注意: 修改 document.domain
的方式只适用于解决 Cookie 和 iframe 的跨源问题。
9.window.name+iframe
浏览器的 window.name 属性有个特性:无论是否同源,只要在同一个浏览器标签或者同一个 iframe 框架打开过的页面,后一个页面可以读取前一个页面设置的 window.name 值(页面刷新后,该值还是存在)。
注意: window.name 的值大小可达 2MB。
根据这个特性,我们可以在主页面内嵌 iframe,先将 iframe 指向一个非同源的页面,然后将 iframe 指向一个与主页面同源的中间页面,再主页面访问同源是中间页面的 window.name 值,间接实现非同源页面的通信。
比如:http://localhost:5200/23-commumication(A).html
要获取非同源页面 http://localhost:5300/23-commumication(B).html
的数据:
首先,在 23-commumication(B).html
页面,将数据赋值给 window.name
:
其次,在 23-commumication(A).html
内嵌一个指向 23-commumication(B).html
的 iframe
标签,在 23-commumication(B).html
文件加载完成后,会执行文件中的 window.name
赋值语句。
注意: 由于 23-commumication(A).html
和 23-commumication(B).html
是非同源页面,无法通过 frame.contentWindow.name
直接获取数据。如果执行上面注释的 console.log(frame.contentWindow.name)
,会报错:
然后,创建一个中间页面 http://localhost:5200/proxy.html
(也可以是不存在的页面,会报 404 错误,但不影响功能),然后将 23-commumication(A).html
中 iframe
标签的 src
指向这个中间页面。
由于 proxy.html
和 23-commumication(B).html
是在同一个 iframe 中打开的,它们共享 window.name
。而 proxy.html
和 23-commumication(A).html
是同源的,23-commumication(A).html
可以通过 frame.contentWindow.name
获取 proxy.html
的 window.name
值,也就是 23-commumication(B).html
的 window.name
值,如此,间接实现了的 23-commumication(A).html
和 23-commumication(B).html
的非同源通信。
修改 getCrossOriginData
方法如下:
总结: 该方案实现的关键在于 proxy.html
文件,它和 23-commumication(B).html
是在同一个 iframe 访问的,并且它和 23-commumication(A).html
是同源的。
同理,该方法也可以实现服务端跨源数据请求:服务端需要提供一个页面地址,并将需要返回的数据赋值给 window.name
属性。客户端调用如下方法获取数据:
10.location.hash+iframe
hash 指的是 URL 的 #
号后面的部分。URL 的 hash 变化,浏览器不会刷新页面。
在父窗口中,通过 iframe 内嵌子窗口。父窗口将数据写入子窗口 URL 的 hash 中,子窗口可通过监听 hashchange
事件获取:
同理,子窗口也可以向父窗口的 hash 写入数据,被父窗口的 hashchange
事件获取:
注意: 只有当 hash 值有变化才能触发 message
事件,即如果设置的 hash 值与当前链接相同,不会触发 message
事件。
注意: window.parent.location.href
是唯一的可以跨源访问的 Location 对象属性,且该属性只能跨源赋值,不能跨源取值。
常见问题
HTTP请求跨源错误原因
服务端设置的
Access-Control-Allow-Origin
不匹配;服务端禁用了
OPTIONS
方法;HTTP 请求头部扩展了字段,服务端的响应头部没有添加允许;
HTTP 请求被服务端重定向到一个不允许跨源的地址;
浏览器禁止跨源请求数据。如:IE 浏览器可以在
工具 -> Internet 选项 -> 安全 -> 自定义级别 -> 其他
,设置是否启用 【通过域访问数据源】。
HTTP请求无法携带Cookie
客户端没有设置
withCredentials = true
;服务端响应头没有
Access-Control-Allow-Credentials: true
,或者Access-Control-Allow-Origin
设置为通用符\*
。跨源发送 Cookie,Access-Control-Allow-Origin
不能设为通用符,必须指定明确的、与请求网页一致的域名。Cookie 的
sameSite
属性没有设置为none
;浏览器禁止了跨源发送 Cookie。
chrome:打开
chrome://flags/
将SameSite by default cookies
、Cookies without SameSite must be secure
两项设置成 Disable;IE:``设置 -> 隐私和安全性 -> cookie -> 不阻止cookie`。
使用了 Mock 等第三方包。
Canvas绘制图片的跨源问题
Canvas调用 getImageData()
或 toDataURL()
对图片操作的时候,会出现跨源问题,而且跨源问题还不止一层。
方法一: 图片储服务器设置允许跨源,客户端将 img
标签的 crossOrigin
属性设置为 anonymous
:
方法二: 改用 Ajax 加载图片数据。即由服务端向图片存储服务器获取图片数据,转换成 base64 返回客户端。如果是请求频繁的图片,可以考虑由服务端转储到本地允许跨源的服务器上,返回转存后的图片链接到客户端。
json及jsonp的区别
JSON 是一种数据交换格式,而 JSONP 是一种依靠开发人员的聪明才智创造出的一种非官方跨源数据交互协议。
参考链接
最后更新于