使用CDN的时候遇到了跨域的问题,在网上找解决方法都是说在nginx里面加上请求头,然而设置了请求头后发现还是没有解决问题。所以为了解决问题,并且彻底弄清楚这其中的原理,花了半天时间仔细分析了一下。
一、什么是跨域
1.1 跨域描述
跨域是一种安全机制,使浏览器只能在页面内执行同源站点的脚本文件,避免出现跨站脚本调用。
所谓同源,指的是协议、域名和端口都相同的地址,三者任意条件不满足则视为跨域。
如:
http://www.a.com/
和https://www.a.com
:协议不同,跨域访问。http://www.a.com
和http://www.b.com
:域名不同,跨域。http://www.a.com:8080
和http://www.a.com:8081
:端口不同,跨域。
在跨域情况下,浏览器会抛出以下错误信息:
跨域往往会给用户带来太多的不安全(如CSRF攻击)因素,所以浏览器出于安全考虑是禁止跨域访问的。
1.2 跨域的解决方案
如果A(域名https://www.baidu.com)站希望访问到B站的脚本,在B站的响应头中加上Access-Control-Allow-Origin字段。如:
1 |
Access-Control-Allow-Origin: https://www.baidu.com |
其中的https://www.baidu.com也可以改成*
,表示允许所有的站点访问,但可能会产生不安全的因素。
二、测试案例
2.1 准备
为了测试多种环境,准备了一个iis服务器和一个装有nginx的虚拟机,虚拟机地址192.168.234.128,本机地址192.168.123.152。hosts设置了两个域名winiis.cn和vmcentos.cn分别表示本机(192.168.123.152)和虚拟机(192.168.234.128):
1 2 |
192.168.234.128 vmcentos.cn 127.0.0.1 winiis.cn |
测试页面文件index.html,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script> function getHtml() { var httpClient = new XMLHttpRequest(); httpClient.open("GET", "test.txt", true); //test.txt创建于同级目录下 httpClient.send(null); httpClient.onreadystatechange = function () { if (httpClient.readyState == 4 && httpClient.status == 200) { alert("Success!"); } }; } </script> </head> <body> <div align="center"> <button type="button" onclick="getHtml()">测试按钮</button> </div> </body> </html> |
代码的主要功能就是发起一个js请求,请求站点的文本文件内容。
2.2 开启iis服务
同级目录下创建一个test.txt,写入数据123456,在IIS中配置web服务:
开启成功后,在浏览器输入winiis.cn
出现以下页面:
点击按钮会弹框:
2.3 开启nginx服务
拷贝index.html和test.txt到虚拟机的/data/www/cors目录下,然后在nginx中配置一个虚拟主机:
1 2 3 4 5 6 7 |
server{ listen 80; server_name vmcentos.cn; location /{ root /data/www/cors; } } |
在浏览器输入vmcentos.cn同样也出现上面内容。
2.4 跨域访问
修改本机的index.html文件:
1 2 3 |
... httpClient.open("GET", "http://vmcentos.cn/test.txt", true); //设置请求文件为非源站文件 ... |
修改虚拟机中的index.html文件:
1 |
httpClient.open("GET", "http://192.168.123.152/test.txt", true); |
然后分别点击两个页面的按钮,发现都不会有弹框,按F12
控制台有以下错误:
这个错误就是跨域的错误信息,因为本机的域名是winiis.cn,访问的文件地址为vmcentos.cn,两者不同源,产生了跨域。
2.5 解决方案
上面当我们访问test.txt时它不可访问的,准确的说并不是不可访问,而是跨域的时候不能访问,当我们在浏览器输入http://vmcentos.cn/test.txt直接访问是可以访问的:
只是当我们通过浏览器访问时,浏览器为了安全会过滤掉这个请求(注意,拦截是在浏览器做的),抛出错误。要解决这个问题要用到浏览器的CORS策略,在服务端响应头中加上Access-Control-Allow-Origin
字段,字段值设置成允许调用的源站地址。即:
1 |
Access-Control-Allow-Origin: http://winiis.cn |
在nginx中设置CORS:
nginx设置CORS
只要在location代码块中添加以下内容即可,后面是允许的源站地址:
1 |
add_header Access-Control-Allow-Origin http://winiis.cn; |
也可以把http://winiis.cn
写成*
表示允许所有来源的跨域访问,但一般不建议这样做,会引起安全问题。
在iis中设置CORS:
主界面中依次点击HTTP响应标头-添加头:
三、跨域原理
当我们进行跨域访问时,请求头中会加上origin
和refer
字段标明该连接的来源(普通的请求中是没有的),浏览器就会通过这个来判断是否是跨域。例如上面在本机的页面中点击按钮访问vmcentos.cn/test.txt
时,会加上以下的头部:
如果服务端设置了CORS
策略,当服务端收到请求后,添加上Access-Control-Allow-Origin
给浏览器返回。在这个过程里,服务端并不会进行跨域判断,只会给予响应然后加上相应的头部,不管是不是跨域请求,服务端都会返回数据。这一点可以从下图中看出,虽然跨域了,但是服务端依旧返回了123456
。
其实所有的跨域操作都是在浏览器中完成的,它收到响应后,会先判断是否为跨域链接,如果跨域再判断Access-Control-Allow-Origin
字段是否满足条件,满足就正常运行,不满足就会抛出错误,不加载资源。
跨域问题的出现时机
并不是所有页面内不同源的文件就会出现跨域问题,像图片文件这种使用<img>
标签引用的就不会出现这种情况。会引起跨域问题的情形为:
- Invocations of the XMLHttpRequest or Fetch APIs in a cross-site manner, as discussed above.
- Web Fonts (for cross-domain font usage in @font-face within CSS), so that servers can deploy TrueType fonts that can only be cross-site loaded and used by web sites that are permitted to do so.
- WebGL textures.
- Images/video frames drawn to a canvas using drawImage.
- Stylesheets (for CSSOM access).
- Scripts (for unmuted exceptions).
这里主要是涉及到了一些XMLHttpRequest
请求、网页字体以及脚本文件等等,它们在进行不同源访问时会导致跨域,而我们经常使用CDN
的时候都会出现跨域问题也就是因为网页内引用了某些css字体导致的。
评论