产生Ajax跨域的三要素
- 浏览器限制:浏览器出于安全考虑,对xhr请求进行限制
- XHR请求:浏览器只会限制xhr(XmlHttpRequest)请求,只要不是xhr请求就不会有跨域问题
- 跨域条件:域名、端口、协议任何一个不相同,浏览器会认为是跨域
备注:跨域并不是服务器不允许前端调用,可以从调试工具中发现xhr请求是有返回值的。
解决跨域问题
- 修改浏览器配置:修改浏览器设置,放开跨域的限制
- 例如:chrome –disable-web-security方式启动,chrome浏览器则不会限制跨域调用
- 改用jsonp请求:浏览器只会对xhr请求进行限制,jsonp没有限制
- jsonp:p表示pending补充,即jsonp是json的补充。
- jsonp请求与json请求的区别:
- jsonp请求类型为script,而json为xhr。
- jsonp的content-type为application/javascript,而json为application/json
- jsonp的请求url中会带callback参数,callback参数是jsonp的前后端的约定
- jsonp前后端的修改:
- 前端:需要将请求返回结果类型调整为jsonp,示例:$.ajax({dataType: “jsonp”})
- 后端:需要将返回的json对象,调整为js代码(前端将返回结果当js代码执行)
- 跨域条件:
- 被调用方:服务方在请求结果的请求头,告诉请求方允许调用
- 调用方:通过代理的方式,让浏览器觉得请求都是同一个服务端
jsonp协议
- jsonp非官方协议,只是一个http请求约定
- 请求方:发请求时需要传约定参数,默认为callback
- 被请求方:服务方处理请求时,发现参数包含约定参数callback,则在请求返回时将响应结果修改为js函数内容,函数名为callback参数的值,函数的参数为要返回的json对象。
- jsonp修改默认参数callback:
- 前端:$.ajax({jsonp:”callback2”}); // 将默认的callback修改为callback2
- 后端:在json切面类,super(“callback”)修改为super(“callback2”)
- jsonp的原理:
- jquery默认是页面加载时在head头部插入一个
<script src="jsonp_url">
这样的临时标签 - 临时标签只有打断点能看到,页面加载完成时会执行json_url返回的内容,并删除script标签
- 因此jsonp只支持get请求
- jquery默认是页面加载时在head头部插入一个
- jsonp的弊端
- 需要服务端支持,如果服务端不支持,只是修改客户端的dataType是不行的。
- 只支持get请求:有时候需要post请求,但jsonp是不支持的
- 不是xhr请求:本质是script请求,所以无法支持xhr请求的特性,例如:异步调用、事件回调
跨域条件的解决思路
- 请求的处理流程:client -> nginx | apache(HTTP服务器) -> tomcat | jboss(应用服务器)
- client -> client-proxy nginx -> server proxy nginx -> tomcat
- 被调用方解决思路:在response响应头中加入Access-Control-Allow-Origin、Access-Control-Allow-Methods字段
- tomcat实现:
- nginx配置:
- apache配置:
- 跨域请求的特点:
- 请求端会在request header中加入Origin字段,值是当前前端页面的域名信息
- 然后判断服务器的response header是否丰存在Access-Control-Allow的属性
简单请求和非简单请求
- 简单请求:
- 请求类型:GET、HEAD、POST
- 请求Header满足以下两个条件
- 无自定义头
- Content-Type:text/plain、multipart/form-data、application/x-www-form-urlencoded
- 非简单请求:(与简单请求相反)
- 请求类型:PUT、DELETE
- 带自定义请求头的ajax请求
- 请求类型Content-Type:application/json,例如:发送json格式的ajax请求
- 对于跨域请求:简单请求是先发请求后判断,但是对于非简单请求是先判断再发请求。
- 请求类型为OPTION表示预检命令,对于复杂请求,浏览器会先发一次预检命令
被调用方Web Server设置方式
- Access-Control-Allow-Origin = “*” 是无法满足cookie跨域的请求的
- 支持cookie需要在Response响应头加入:Access-Control-Allow-Origin=”实际地址”,例如:”localhost:8081”
- 支持cookie需要在Response响应头加入:Access-Control-Allow-Credentials=”true”
- 通用的跨域调用的处理方式:注意cookie是被调用方的cookie
String origin = request.getHeader("Origin"); // 从请求头中获取客户端的Origin信息
if(StringUtils.isNotEmpty(origin)){
response.setHeader("Access-Control-Allow-Origin", origin); // 判断是否是跨域请求
}
response.setHeader("Access-Control-Allow-Credentials", "true"); // 避免cookie请求报错
String headers = reuqest.getHeader("Access-Control-Allow-Headers");
if(StringUtils.isNotEmpty(headers)){
response.setHeader("Access-Control-Allow-Headers", headers); // 通用的header
}
- Spring框架对跨域的支持:直接在Controller类加上@CrossOrigin注解即可,此注解也可以加在方法上
被调用方Nginx及Apache设置方式
- nginx添加如下配置,被调用方的Nginx服务器配置
server {
listen 80;
server_name xxx.com; // 服务端域名
location /{ // url转发
proxy_pass http://localhost:8080; // 将xxx.com转发至localhost:8080
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600; // option请求缓存时间
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Headers $http_access_control_allow_headers;
if ($request_method = OPTIONS){
return 200; // 预检命令直接返回200
}
}
}
- Apache配置修改与nginx思路一样,具体设值查询下apache的配置说明。
调用方解决方案 - 隐藏跨域
- 调用方的nginx配置如下:
server{
listen 80;
server_name ccc.com;
location / { // 将调用方的ccc.com转发至localhost:8081,调用方需要通过ccc.com访问
proxy_pass http://localhost:8081/; // 域名访问,通过客户端代理解决跨域问题
}
location /ajaxserver {
proxy_pass http://localhost:8080/test; // 将ajax请求统一转发至服务端
}
}
- 注意:此时调用方(客户端)的ajax代码,需要将请求url设置为相对路径,示例:
var baseUrl = "/ajaxserver"; // 正常情况应该是将baseUrl设置为服务端的绝对url,示例:localhost:8080
// 设置为/表示此路径为相对地址,即localhost:8081,这样来看所有的请求就是都走的8081的/ajaxserver转发,就不满足跨域条件
$.getJSON(baseUrl + "/getData").then(function(){});
参考资料:http://www.cnblogs.com/oneword/archive/2012/12/03/2799443.html