最近项目中碰到了这个问题,“如何解决跨域问题?”,我根据网上的资料和实际代码中的应用大概总结如下。
首先我们要理解什么跨域,为什么会出现跨域呢?
跨域:
跨域是由浏览器的同源策略引起的,跨域访问,简单来说就是 A 网站的 js脚本代码试图去访问 B 网站(代码中包括提交内容和获取内容)。但是由于安全原因,跨域访问是被各大浏览器所默认禁止的,而跨域与否的判断是根据同源策略来的。
同源策略:
这里同源策略又分为以下两种:
- DOM同源策略:禁止对不同源页面的DOM进行操作。主要的场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
- Ajax(XmlHttpRequest)同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
接下来我们举例区分是否跨域:
只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。
http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
那么跨域会导致什么呢?
跨域限制主要是为了安全考虑,所以如果网站没有跨域限制,会有什么后果呢?:
- Ajax(XmlHttpRequest)同源策略主要用来防止CSRF攻击。如果没有AJAX同源策略,相当危险,我们发起的每一次HTTP请求都会带上请求地址对应的cookie,那么可以做如下攻击:
- 用户登录了自己的银行页面 http://mybank.com,http://mybank.com向用户的cookie中添加用户标识。
- 用户浏览了恶意页面 http://evil.com。执行了页面中的恶意AJAX请求代码。
- http://evil.com向http://mybank.com发起AJAX HTTP请求,请求会默认把http://mybank.com对应cookie也同时发送过去。
- 银行页面从发送的cookie中提取用户标识,验证用户无误,response中返回请求数据。此时数据就泄露了。
- 而且由于Ajax在后台执行,用户无法感知这一过程。
- DOM同源策略也一样,如果iframe之间可以跨域访问,可以这样攻击:
- 做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com。
- 把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。
- 这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。
所以说有了跨域跨域限制之后,我们才能更安全的上网了。
那么我们开发中如果解决跨域呢?
还有其他几种办法,但是感觉这几种相对来说比较常用。
一、 通过jsonp跨域
二、 跨域资源共享(CORS)
三、 nginx代理跨域
四、 nodejs中间件代理跨域
五、 WebSocket协议跨域
具体解决方案:
一、 通过jsonp跨域
这是最常见的一种跨域方式,其背后原理就是 : 利用了<script>标签不受同源策略的限制,在页面中动态插入了script,script标签的src属性就是后端api接口的地址,并且是通过get的方式将前端回调处理函数名称告诉后端,后端在响应请求时会将回调返还,并且将数据以参数的形式传递回去。
简单来说:就是通过动态创建script标签,然后利用src属性进行跨域。
代码解释:
1.)原生实现:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
服务端返回如下(返回时即执行全局函数):
onBack({"status": true, "user": "admin"})
2.)jquery ajax:
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
好了总结一下用jsonp请求数据的基本流程。
首先在客户端注册一个callback, 然后把callback的名字传给服务器。
服务器先生成 json 数据。
然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp.
将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。
客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里.(动态执行回调函数)
jsonp的缺点
没有关于 JSONP 调用的错误处理。如果动态脚本插入有效,就执行调用;如果无效,就静默失败。失败是没有任何提示的。例如,不能从服务器捕捉到 404 错误,也不能取消或重新开始请求.
JSONP 的另一个主要缺陷是被不信任的服务使用时会很危险。因为 JSONP 服务返回打包在函数调用中的 JSON 响应,而函数调用是由浏览器执行的,这使宿主 Web 应用程序更容易受到各类攻击。
要注意JSONP只支持GET请求,不支持POST请求。所以使用该方式的缺点也就是:请求方式只能是get请求。
二、 跨域资源共享(CORS)
setHeader("Access-Control-Allow-Origin", "*") # 允许所有域名访问,或者
setHeader("Access-Control-Allow-Origin", "http://test.com")# 允许test.com这个域名发过来的所有请求。
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下:3、nginx反向代理中设置proxy_cookie_domain 和 4、NodeJs中间件代理中cookieDomainRewrite参数的设置。
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
这里我就简单的说一说大体流程。
- 对于客户端,我们还是正常使用xhr对象发送ajax请求。
唯一需要注意的是,我们需要设置我们的xhr属性withCredentials为true,不然的话,cookie是带不过去的哦,设置: xhr.withCredentials = true; - 对于服务器端,需要在 response header中设置如下两个字段:
Access-Control-Allow-Origin: http://www.yourhost.com
Access-Control-Allow-Credentials:true
这样,我们就可以跨域请求接口了。
三、 nginx代理跨域
1、 nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *;
}
2、 nginx反向代理接口跨域
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
nginx具体配置:
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
四、 Nodejs中间件代理跨域
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
五、 WebSocket协议跨域
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
总结:最常用的是Jsonp和CORS跨域方式。
我这里放一篇文章https://segmentfault.com/a/1190000011145364,写的很详细,有兴趣的可以去看看,让我对于跨域的理解更深入了,膜拜大神!