一、什么是跨域问题?
1.同源策略:协议+域名+端口一致,否则就算跨域。(localhost与127.0.0.1也算跨域)
2.DOM同源策略:禁止对不同源页面DOM进行操作
3.XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求
二、跨域解决方案之JSOP
1.原理:利用script标签src属性中的链接却可以访问跨域的js脚本的特性,服务端不再返回JSON格式的数据,而是返回一段调用某个函数的js代码,在src中进行了调用,这样实现了跨域。
2.优点&局限性:没有浏览器兼容性问题;只支持GET方法
3.JSONP请求示例:
3.1、前端AJAX请求,注意type、dataType、jsonp字段(jsonp可以不指定,默认就是callback)
$.ajax({
type: "GET",
url: url,
dataType: "jsonp",
jsonp: "callback",
data: {
dataType: "1", //告诉服务器端这是JSONP请求
json: json //业务请求数据,JSON格式
},
success: function(data) {
//请求成功后的处理
},
error: function(status) {
//请求失败后的处理
}
})
3.2、后端返回的数据格式处理
/**
* 将指定对象封装成JSONPObject,错误时可以指定错误码.
*
* @param pObj 被封装对象
* @return 封装后的JSONPObject
*/
public Object getJSONResultObj(Object pObj, Boolean pIsSuccess, String pMessage, String pErrorCode) {
HttpServletRequest request = this.getRequest();
String callBackFun = request.getParameter("callback");
//0:JSON、1:JSONP
String dataType = request.getParameter("dataType");
// 作成JSON处理结果对象
JSONResultVo result = new JSONResultVo();
result.setResult(pIsSuccess ? JSONResultVo.RESULT_SUCCESS : JSONResultVo.RESULT_FAIL);
result.setMessage(pMessage);
result.setErrorCode(pErrorCode);
result.setData(pObj);
if ("1".equals(dataType)) {
//回调方法未指定时
if (StringUtil.isEmpty(callBackFun)) {
result.setResult(JSONResultVo.RESULT_FAIL);
result.setMessage("请求错误:未指定callback");
}
return callBackFun + "(" + getJSON(result).toString() + ");";
} else {
return getJSON(result);
}
}
3.3、请求返回的数据格式:
可以看到,返回的一个调用jsonp1方法的JS脚本,传入方法的参数才是我们真正想要的数据;提取数据并转换成对象类型这些Jquery的ajax已经封装好了,并作为success方法的参数,我们可以直接使用。
三、跨域解决方案之CORS
CORS分为简单请求和非简单请求两种。
1、简单请求:
1.1、简单请求必需满足的条件
①.请求方法:只能是HEAD、GET、POST中的一种②.请求头必须在下面的范围内:
· Accept
· Accept-Languange
· Content-Language
· Last-event-ID
· Content-Type(只限于三个值application/x-www-form-urlencode、multipart/form-data、text/plain)
1.2、浏览器发送的请求
请求头增加Origin字段,该字段表示请求源的地址即当前页面的域名(服务器端一般以此判断是否跨域)。1.3、服务器的响应信息
①.Access-Control-Allow-Origin:允许跨域请求时,该值应该包含请求的Origin字段的值(只能是单个),或者一个【*】 ,表示接受任意域名的请求。对于只能设置单个请求源的限制,可以通过设置变量值动态设置Origin的值解决。
②.Access-Control-Allow-Credentials(可选):是否允许发送Cookie。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段为true,另一方面,开发者必须在AJAX请求中设置withCredentials属性为true(此时Access-Control-Allow-Origin不能为 * )。
2、非简单请求
2.1、用于哪些场合?
2.2.1、什么是预检
2.2.2、预检请求有什么特殊的地方
(2)请求头除了Origin字段,包含另外两个特殊的字段Access-Control-Request-Method:请求使用哪种方法、Access-Control-Request-Headers:额外发送的头信息字段
(3)除了ccess-Control-Allow-Origin,响应头包含一些特殊的字段:Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Allow-Credentials(可选)、Access-Control-Max-Age(可选)
2.2.3、预检请求成功示例:
$.ajax({
type: "DELETE",
url: url,
data: {
dataType: "2"
},
headers: "My-Custom-Header", //自定义头
success: function(data) {
$(".result").text("ID:" + data.data.id + " NAME:" + data.data.name);
},
error: function(status) {
$.alert("您的网络不给力哦");
}
})
下面是浏览器自动发送的预检请求
如果预检没通过,浏览器会报下面额错,告诉预检失败
2.2.4、SpringMVC支持CORS的三种方式:
(1)使用@CrossOrigin注解(4.2及以上)
@RequestMapping(value = "/{id}", produces = CHARSET_PRODUCES, method=RequestMethod.DELETE)
@ResponseBody
@CrossOrigin(origins="*",allowedHeaders="My-Custom-Header",methods=RequestMethod.DELETE)
public Object testCors(String json, @PathVariable("id")String id,
HttpServletResponse response, HttpServletRequest request) {
Map<String, String> data = new HashMap<String, String>();
data.put("id", id);
data.put("name", "测试");
return success(data);
}
(2)CORS全局配置:可以以path为单位定义多个映射(4.2及以上)
基于XML的配置
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://djf.eastallcar.com"
allowed-methods="POST"
allowed-headers="My-Custom-Header"
allow-credentials="true"
max-age="1800"/>
<mvc:mapping path="/**"
allowed-origins="*"
allowed-methods="GET,POST,DELETE"
allowed-headers="My-Custom-Header"
allow-credentials="true"
max-age="1800"/>
</mvc:cors>
基于JAVA代码的配置
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://djf.eastallcar.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("My-Custom-Header1", "My-Custom-Header2")
.exposedHeaders("My-Custom-Header1", "My-Custom-Header2")
.allowCredentials(false).maxAge(3600);
}
}
(3)增加一个继承自OncePerRequestFilter的过滤器
/**
* 跨域请求过滤器.
*/
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "http://djf.eastallcar.com");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "My-Custom-Header");
response.setHeader("Access-Control-Max-Age", "60");
filterChain.doFilter(request, response);
}
}
2.2.5、CORS个人使用体会:
(1)使用简单请求还是非简单请求是浏览器根据请求方法和头信息决定的,与服务器端无关;(2)CORS是为了解决浏览器端的跨域限制,不是解决服务器资源安全的方式;可以认为浏览器对跨域资源请求的流程是对资源请求的安全的一个二次校验,这个校验需要服务器端提供支持情况(通过响应头告诉浏览器支持的请求源、方法、头)。
(3)部分浏览器的老版本不支持CORS,适用于对兼容性要求不高的项目。