Spring Boot 对跨源(域)资源共享
的支持(CORS)
首先,什么是跨域资源?
跨源资源共享(CORS) (或通俗地译为跨域资源共享) 是一种基于 HTTP 头
的机制,该机制通过允许服务器标示除了它自己以外的其他 origin
(域,协议,端口),这样浏览器就可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否辉允许要发送的真是请求,该机制通过浏览器发起一个服务器托管的跨源资源的预检
请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
例子: 运行在 http://domain-a.com
的JavaScript代码使用XMLHttpRequest来发起一个到https://domain-b.com/data.json
的请求。
Spring Boot 对 CORS 的介绍:是一个由大多数浏览器实现的 W3C 规范,它允许你以一种灵活的方式指定哪种跨域请求是合法的,而不是使用一些不那么安全和强大的方法,如 IFRAME 或 JSONP。
专业解释,强烈推荐阅读!!!
关于CORS 更详细的介绍请参考 MDN 跨源资源共享(CORS)
跨域请求错误以什么形式出现呢?
演示:此 testCrossOrigin.html位于 http://localhost:8080
域下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
var invocation = new XMLHttpRequest();
var url = 'http://localhost:8333/testCrossOrigin';
// 简单请求
function callOtherDomain(append) {
if (invocation) {
invocation.open('GET', url+append, true);
invocation.onloadend = () => {
console.log(invocation.responseText);
document.write(invocation.responseText);
};
invocation.send();
}
}
</script>
</head>
<body>
<button onclick="callOtherDomain('')">testCrossOrigin</button>
</body>
</html>
为了模拟跨域, 在http://localhost:8333
域下创建 testCrossOrigin
Controller
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestCrossOriginController {
@ResponseBody
@RequestMapping("/testCrossOrigin")
public String testCrossOrigin() {
String s = "ok";
return s;
}
}
在浏览器中打开 http://localhost:8080/testCrossOrigin.html
点击testCrossOrigin 按钮
出现错误信息:
可以看到 请求已被CORS策略阻止
Access to XMLHttpRequest at 'http://localhost:8333/testCrossOrigin' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Spring Boot 添加对跨域的支持
Spring Boot 中使用 @CrossOrigin 注解支持 CORS
修改上例 TestCrossOriginController
类,添加@CrossOrigin(originPatterns = "http://localhost:8080")
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@CrossOrigin(originPatterns = "http://localhost:8080")
public class TestCrossOriginController {
@ResponseBody
@RequestMapping("/testCrossOrigin")
public String testCrossOrigin() {
String s = "ok";
return s;
}
}
浏览器测试
ok~! 对于一个简单的跨域请求 已经ok。
预检请求
“需预检的请求”要求必须首先使用 OPTIONS
方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。即请求时向服务器发送自定义,或者 HTTP 规范请求头,以供服务器对此请求检查。
演示:
此 testCrossOrigin.html位于 http://localhost:8080
域下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
var invocation = new XMLHttpRequest();
var url = 'http://localhost:8333/testCrossOriginPreInspection';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
// 预检请求
function callOtherDomain(){
if(invocation)
{
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
invocation.setRequestHeader('Content-Type', 'application/xml');
invocation.onloadend = () => {
console.log(invocation.responseText);
document.write(invocation.responseText);
};
invocation.send(body);
}
}
</script>
</head>
<body>
<button onclick="callOtherDomain()">testCrossOrigin</button>
</body>
</html>
在http://localhost:8333
域下创建 testCrossOrigin
Controller
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@CrossOrigin(originPatterns = "http://localhost:8080")
public class TestCrossOriginController {
@CrossOrigin(originPatterns = "http://localhost:8080", allowedHeaders = {"X-PINGOTHER", "Content-Type"},
methods = {RequestMethod.GET,RequestMethod.POST,RequestMethod.OPTIONS})
@RequestMapping("/testCrossOriginPreInspection")
public String testCrossOriginPreInspection(HttpServletRequest request,@RequestBody String requestBody) {
request.setAttribute("requestBody",requestBody);
return "formCrossOrigin";
}
}
浏览器测试
服务器可以接收到请求时的数据,并且执行了响应的Controller。
而当我们对 testCrossOrigin.html 中 script 中 invocation 设置的请求头做出更改时
原
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
修改
invocation.setRequestHeader('X1-PINGOTHER', 'pingpong');
测试浏览器请求
受到了 CORS 策略的阻止
Spring Boot 添加全局 CORS
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}