1. 是什么?
-
同域:相同域名,端口相同,协议相同。
-
跨域:浏览器对于javascript的同源策略的限制,例如a.cn下面的js不能调用b.cn中的js,对象或数据(因为a.cn和b.cn是不同域),所以跨域就出现了。
-
同源策略:请求的url地址必须与浏览器上的url地址处于同域上。
比如:我在本地上的域名是study.cn,请求另外一个域名一段数据。就会报错404。
为什么有同源策略保护:如果浏览器对javascript没有同源策略的保护,那么一些重要的机密网站将会很危险。
如果域名是study.cn/json/jsonp/jsonp.html,那么有以下几种情况
请求地址 | 形式 | 结果 |
---|---|---|
http://study.cn/test/a.html | 同一域名,不同文件夹 | 成功 |
http://study.cn/json/jsonp/jsonp.html | 同一域名,统一文件夹 | 成功 |
http://a.study.cn/json/jsonp/jsonp.html | 不同域名,文件路径相同 | 失败 |
http://study.cn:8080/json/jsonp/jsonp.html | 同一域名,不同端口 | 失败 |
https://study.cn/json/jsonp/jsonp.html | 同一域名,不同协议 | 失败 |
2. 怎么解决跨域问题
1. jsonp
-
jsonp:全称是JSON with Padding,是为了解决跨域请求资源而产生的解决方案,是一种依靠开发人员创造出的一种非官方跨域数据交互协议。
一个是描述信息的格式,一个是信息传递双方约定的方法。 -
jsonp的产生:
- AJAX直接请求普通文件存在跨域无权限访问的问题
- 我们在调用js文件的时候又不受跨域影响,比如引入jquery框架或者是调用图片的时候
- 凡是拥有scr这个属性的标签都可以跨域例如<script><img><iframe>
- 如果想通过纯web端跨域访问数据只有一种可能,那就是把远程服务器上的数据装进js格式的文件里
- 而 json又是一个轻量级的数据格式,还被js原生支持
- 为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback 参数给服务端
- 2.2.1 基于script标签实现跨域
例子1:在我的项目http://localhost:9090/test.html下请求另一个服务的js文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
var message = function(data) {
alert(data[0].title);
};
</script>
<script type="text/javascript" src="http://localhost:8080/test_war%20exploded/message.js"></script>
</head>
<body>
<div id='testdiv'></div>
</body>
</html>
另一个项目的js文件
message([
{"id":"1", "title":"天津新闻联播,雷人搞笑的男主持人"},
{"id":"2", "title":"楼市告别富得流油 专家:房价下跌是大概率事件"},
{"id":"3", "title":"法国人关注时事 八成年轻人每天阅读新闻"},
{"id":"4", "title":"新闻中的历史,历史中的新闻"},
{"id":"5", "title":"东阳新闻20140222"},
{"id":"6", "title":"23个职能部门要增加新闻发布频次"},
{"id":"7", "title":"《贵州新闻联播》 中国美丽乡村"},
{"id":"8", "title":"朝韩离散家属团聚首轮活动结束"},
{"id":"9", "title":"索契冬奥会一天曝出两例兴奋剂事件"},
{"id":"10", "title":"今天中国多地仍将出现中度霾"}
]);
这样就实现跨域成功了,因为服务端返回数据时会将这个callback参数(message)作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
注意:跨域请求是只能是get请求不能使用post请求
例子2:jsonp形式的Ajax请求
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="./js/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
var name = 'chenshishuo';
var sex = 'man';
var address = 'shenzhen';
var looks = 'handsome ';
$.ajax({
type : 'get',
url:'http://192.168.31.137/train/test/testjsonp',
data : {
name : name,
sex : sex,
address : address,
looks : looks,
},
cache :false,
jsonp: "callback",
jsonpCallback:"success",
dataType : 'jsonp',
success:function(data){
alert(data);
},
error:function(data){
alert('error');
}
});
});
</script>
</head>
<body>
<input id='inputtest' value='546' name='inputtest'>
<div id='testdiv'></div>
</body>
</html>
jsonp 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
jsonpCallback 自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
例子3:通过iframe来跨子域
基于iframe实现的跨域要求两个域具有aa.xx.com,bb.xx.com 这种特点,
也就是两个页面必须属于一个基础域(例如都是xxx.com),使用同一协议和同一端口,这样在两个页面中同时添加document.domain,就可以实现父页面调用子页面的函数。
aa.xx.com下的a.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
function test() {
alert(document.getElementById('a').contentWindow);
}
</script>
</head>
<body>
<iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
</html>
bb.xx.com下的b.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
</script>
</head>
<body>
我是b.study.cn的body
</body>
</html>
原理就是让这个 iframe载入一个与你想要通过ajax获取数据的目标页面处在相同的域的页面,所以这个iframe中的页面是可以正常使用ajax去获取你要的数据的,然后就是通过我们刚刚讲得修改document.domain的方法,让我们能通过js完全控制这个iframe,这样我们就可以让iframe去发 送ajax请求,然后收到的数据我们也可以获得了。
2 空iframe加form
jsonp只可以发送get请求,因为本身javascript加载资源就是用get请求,所以要发post请求怎么办?
const requestPost = ({url, data}) => {
// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
console.log('post success')
})
form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()
// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
3 CORS
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。CORS有两种请求,简单请求和非简单请求。
- 简单请求
- 请求方式限制:GET POST HEAD
- HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form- urlencoded、multipart/form-data、text/plain
- 原理
发送请求,浏览器会在在header信息里面多加一个字段:
key:origin
value:协议+域名+端口(如https://localhost:8080)
服务器根据对象里的origin值来决定是否同意这次请求
如果请求通过
请求通过后,服务器会在header里多增加几个字段:
Access-Control-Allow-Origin: https://localhost:8080
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Token
- 解释:
1.Credentials是否允许包含cookie,如果返回了true,那么浏览器发送请求体的时候要添加:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
如此这般,才能向服务器发送cookie
2.Access-Control-Expose-Headers是多返回的字段名,根据实际需求改变
如果请求失败
说明之前发送Origin的value不在许可范围内
服务器会返回一个正常的HTTP回应
浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段
从而抛出一个无法跨域的异常(可能是200,所以无法识别
- 非简单请求
非简单请求的方法就是Put,Delete,或者Content-Type是application/json
非简单请求在发出CORS请求之前,会增加一次HTTP查询请求,也叫预检请求,预检请求成功了,浏览器才能发出XMLHttpRequest,否则报错
如果浏览器发现这是一个非简单请求
就会先发送一个预检请求:
OPTIONS /cors HTTP/1.1
Origin: http://localhost:8088
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: token
预测请求的方法名叫OPTIONS
Origin和简单请求一样
Access-Control-Request-Method是请求方法
Access-Control-Request-Headers是额外发送的头信息字段
(tip:预检请求一般缓存下来,以便不需要在每次请求都发送)
如果预检请求通过
我们来看下服务器发出的回应
HTTP/1.1 200 OK
Date: Nov, 01 Dec 2017 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:8088
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: token
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
如果预检请求失败
服务器会返回一个HTTP回应(没有CORS的头信息字段)
并在console打出not allowed by Access-Control-Allow-Origin异常
预检请求通过以后
浏览器发每次出的CORS请求就和简单请求一样
会有一个origin字段
服务器每次回应都会有Access-Control-Allow-Origin头信息字段
代码实现:
前端ajax请求:
$.ajax({
type: "POST",
url: baseUrl + "/post",
dataType: 'json',
crossDomain: true,
xhrFields: {
withCredentials: true
},
data: {
name: "name_from_frontend"
},
success: function (response) {
console.log(response)// 返回的 json 数据
$("#response").val(JSON.stringify(response));
}
});
crossDomain: true是指开启跨域
后端mvc配置:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**/*").allowedOrigins("*");
}
}
4 Nginx代理
请求的时候还是用前端的域名,然后Nginx帮我们把这个请求转发到真正的后端域名上,避免跨域.
但如果后端接口是一个公共的API,比如一些公共服务获取天气什么的,前端调用的时候总不能让运维去配置一下Nginx,如果兼容性没问题(IE 10或者以上),CROS才是更通用的做法。

网友评论