在浏览器同源策略限制下,向 不同源(不同协议、不同域名或者不同端口) 发送XHR请求
浏览器认为该请求不受信任,禁止请求,具体表现为请求后不正常响应。
产生跨域时,http请求能实际访问到目标资源并且响应成功,但是由于浏览器的同源策略限制,导致响应被拦截,并在控制台提示,现象为:
不同源示例:
特别注意两点:
第一、如果是协议和端口造成的跨域问题“前台”是无能为力的
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上,通俗来说localhost和127.0.0.1不同源。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。
同源策略的目的是保护用户的安全和隐私。它防止恶意网站通过跨域请求访问用户的敏感信息,或者利用其他网站上的漏洞进行攻击。
同源策略施加了以下限制:
浏览器解析HTML文档,当解析到< script >元素的src属性时,会触发一个GET请求,而浏览器不检查src属性值的URL与网页服务器地址是否同源,利用这个漏洞(姑且认为是漏洞吧),服务端可以将JSON数据组装成JS文件流返回给网页。而只是简单的返回JSON数据还不行,需要网页一个已经加载到浏览器内存的JS函数callback(就是padding部分),用来接收JSON数据。浏览器接收到JS文件流,JS解释器执行解析,完成callback函数的执行。
前端伪代码:
JSONP
后端伪代码:
public String getFun(String funName){
return funName + '(' + JSON.toJsonString(data) + ')';
}
注意:
JSONP是通过< script >触发的GET请求,和ajax无半毛钱关系,JSONP请求数据的方式不能叫做 ajax,因为它没有使用xhr发送请求(xhr,即XMLHttpRequest实例化对象)。
web sockets是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)
web sockets原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。
只有在支持web socket协议的服务器上才能正常工作。
伪代码:
var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
var data = event.data;
}
1) http://a.com/index.html中的代码:
2) http://b.com/index.html中的代码:
1)在http://www.a.com/a.html中:
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://www.script.a.com/b.html';
ifr.display = none;
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
//在这里操作doc,也就是b.html
ifr.onload = null;
};
2)在http://www.script.a.com/b.html中:
document.domain = 'a.com';
原理是利用location.hash来进行传值。
假设域名http://a.com下的文件cs1.html要和http://cnblogs.com域名下的cs2.html传递信息。
1)cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向http://cnblogs.com域名下的cs2.html页面
2)cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据
3)同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一旦有变化则获取获取hash值
注:由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于http://a.com域名下的一个代理iframe
代码如下:
先是http://a.com下的文件cs1.html文件:
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo';
document.body.appendChild(ifr);
}
function checkHash() {
try {
var data = location.hash ? location.hash.substring(1) : '';
if (console.log) {
console.log('Now the data is '+data);
}
} catch(e) {};
}
setInterval(checkHash, 2000);
www.cnblogs.com域名下的cs2.html:
//模拟一个简单的参数处理操作
switch(location.hash){
case '#paramdo':
callBack();
break;
case '#paramset':
//do something……
break;
}
function callBack(){
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全机制无法修改parent.location.hash,
// 所以要利用一个中间的cnblogs域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意该文件在"a.com"域下
document.body.appendChild(ifrproxy);
}
}
http://a.com下的域名cs3.html
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
window.name 的美妙之处:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。
1)创建http://a.com/cs1.html
2)创建http://a.com/proxy.html,并加入如下代码
<head>
<script>
function proxy(url, func){
var isFirst = true,
ifr = document.createElement('iframe'),
loadFunc = function(){
if(isFirst){
ifr.contentWindow.location = 'http://a.com/cs1.html';
isFirst = false;
}else{
func(ifr.contentWindow.name);
ifr.contentWindow.close();
document.body.removeChild(ifr);
ifr.src = '';
ifr = null;
}
};
ifr.src = url;
ifr.style.display = 'none';
if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc);
else ifr.onload = loadFunc;
document.body.appendChild(iframe);
}
</script>
</head>
<body>
<script>
proxy('http://www.baidu.com/', function(data){
console.log(data);
});
</script>
</body>
3 在http://b.com/cs1.html中包含:
Nginx作为反向代理服务器,接收来自客户端的http请求,并将其转发到另一个或多个服务器上。
Nginx通过配置相应的代理规则,拦截匹配到的前缀代理到要跨域访问的实际地址上,配置相关如下:
server {
listen 80;
server_name poisson_server;
location / {
proxy_pass http://192.168.142.51:8080/;
}
}
CORS全称Cross-Origin Resource Sharing,是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制。
只要服务器明确表示 允许 ,则校验通过。服务器明确表示 拒绝 或 没有表示,则校验 不通过。
当请求同时满足以下条件时,浏览器会认为它是一个简单请求:
请求方法属于下面的一种:
get
post
head
请求头仅包含安全的字段,常见的安全字段如下:
Accept
Accept-Language
Content-Language
Content-Type
DPR
Downlink
Save-Data
Viewport-Width
Width
请求头如果包含Content-Type,仅限下面的值之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
交互规范:
当浏览器判定某个ajax跨域请求是简单请求时,会发生以下的事情:
请求头中会自动添加Origin字段
服务器响应头中应包含Access-Control-Allow-Origin该字段的值可以是:
*:表示我很开放,什么人我都允许访问
具体的源:比如http://my.com,表示我就允许你访问
浏览器校验ajax请求是非简单请求后,会向服务器发送预检请求,具体流程如下:
OPTIONS /api/user HTTP/1.1
Host: crossdomain.com
...
Origin: http://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type
预检请求有以下特征:
服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式
HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
...
预检请求响应头中的各属性含义:
完成预检之后,后续的处理与简单请求一致
在ajax请求头中附加cookie信息,服务器允许前提下会在响应头中添加Access-Control-Allow-Credentials: true进行明确告知,如果服务器未进行明确告知,浏览器仍将此次请求作为跨域拒绝处理
注意:对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*。
详情点击
默认Google浏览器
1、在桌面找到chrome 浏览器图标,右键,属性标签页中。
2、在目标输入框中加上 --disable-web-security即可。
比较体 | 缺点 | 优点 | 适用场景 |
---|---|---|---|
JSONP | 1. 会打乱服务器的消息格式 2.只能完成GET请求 |
实现简单 | 适用于数据量较小,不需要特别保密的场景 |
web socket(s) | 1. 需要服务端配合实现 2. 只能处理文本数据、不稳定 |
可以实现双向通信,实时更新 | 适用于聊天室、游戏等实时性较高的场景 |
nginx反向代理 | 需要配置服务器,增加了服务器端的压力 | 能够支持跨域请求、保护了 API 的安全性 | 用于解决前端直接访问跨域 API 的问题 适用于请求量较大、需要保密的场景。 |
CORS | 需要服务端配合设置响应头、性能稍低 非简单请求下,有次预检的操作,增加访问次数 |
能够支持跨域请求、支持多种数据类型 | 能够支持跨域请求,同时支持多种数 适用于大部分的场景。 |
postMessage | 需要进行安全性检查、可能存在 XSS 攻击风险 | 可以进行不同窗口、不同域名之间的数据传递 | 适用于嵌套页面、多窗口通信等场景 |
document.domain | 只适用于主域名相同、子域名不同的情况下 | 实现简单、不需要浏览器支持 | 适用于主域名相同、子域名不同的情况下 |
iframe | 存在安全风险、容易遭受 CSRF 攻击 | 能够实现跨域通信 | 适用于嵌套页面、跨域数据传输等场景 |
的安全性| 用于解决前端直接访问跨域 API 的问题
适用于请求量较大、需要保密的场景。 |
| CORS | 需要服务端配合设置响应头、性能稍低
非简单请求下,有次预检的操作,增加访问次数 |能够支持跨域请求、支持多种数据类型| 能够支持跨域请求,同时支持多种数
适用于大部分的场景。 |
| postMessage | 需要进行安全性检查、可能存在 XSS 攻击风险 |可以进行不同窗口、不同域名之间的数据传递| 适用于嵌套页面、多窗口通信等场景 |
| document.domain | 只适用于主域名相同、子域名不同的情况下 |实现简单、不需要浏览器支持| 适用于主域名相同、子域名不同的情况下 |
| iframe | 存在安全风险、容易遭受 CSRF 攻击 |能够实现跨域通信| 适用于嵌套页面、跨域数据传输等场景 |
注:CORS是官方推荐跨域解决方案,但可根据实际场景自定义解决方案