37.1 跨域请求概述
说到跨域请求,就不得不说说浏览器的同源策略(Same origin policy)。
所谓同源是指发起请求所在域的协议、域名以及端口和资源所在域的都相同。
同源策略是由 Netscape 提出的一个著名的安全策略,它是浏览器最核心也最基本的安全功能——浏览器限制从JS脚本发起的非同源HTTP请求。 类似 XMLHttpRequest 和 Fetch API 都遵循同源策略。
因为同源策略是基于对安全方面的考虑而提出来的,所以这个策略本身是非常必要的。但在实际开发中,由于各种原因又经常有跨域的需求。传统的解决跨域需求的方案是 JSONP。由于它只支持 GET 请求,所以这个方案再在 RESTful 时代这基本上没什么用。
为规范解决跨域请求,W3C 提出了 CORS(Cross-origin resource sharing,跨域源资源共享)标准。它是一份浏览器的技术规范,定义了 Web 服务从不同域传递沙盒脚本的方法,以避开浏览器的同源策略。
37.2 准备实验项目
创建两个普通的 Spring Boot 项目,分别命名为 provider 和 consumer 。前者配置端口为 8080,后者配置配置为 8081,然后在 provider 上提供两个 hello 接口,一个 get,一个 post:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "This's GetMapping hello";
}
@PostMapping("/hello")
public String hello2() {
return "This's PostMapping hello";
}
}
在 consumer 的 resources/static 目录下创建一个 html 文件,发送一个简单的 ajax 请求,如下:
<div id="app"></div>
<input type="button" onclick="getButtonClick()" value="get_button">
<input type="button" onclick="postButtonClick()" value="post_button">
<script>
function getButtonClick() {
$.get('http://localhost:8080/hello', function (msg) {
$("#app").html(msg);
});
}
function postButtonClick() {
$.post('http://localhost:8080/hello', function (msg) {
$("#app").html(msg);
});
}
</script>
然后分别启动两个项目,发送请求按钮,观察浏览器控制台如下:
Access to XMLHttpRequest at 'http://localhost:8080/hello' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
37.3 支持跨域请求的基本方法
37.3.1 用注解设置支持跨越请求
在 Spring 框架中,对于 CORS 也提供了相应的解决方案(尤其在 Spring Boot 中得倒了简化),无论是单纯的跨域,还是结合 Spring Security 之后的跨域,都变得比较容易。
首先在 provider通过 @CrossOrigin
注解配置某一个方法接受 另一个域的请求,如下:
@RestController
public class HelloController {
@CrossOrigin(value = "http://localhost:8081")
@GetMapping("/hello")
public String hello() {
return "This's GetMapping hello";
}
@CrossOrigin(value = "http://localhost:8081")
@PostMapping("/hello")
public String hello2() {
return "This's PostMapping hello;
}
}
这个注解表示这两个接口接受来自 http://localhost:8081 地址的请求,配置完成后,重启 provider ,再次发送请求,consumer 能够拿到数据。
此时观察浏览器请求网络控制台,可以看到响应头中多了如下信息:
这个表示服务端愿意接收来自 http://localhost:8081 的请求,拿到这个信息后,浏览器就不会再去限制本次请求的跨域了。
37.3.2 全局开启支持跨越请求
显然在 provider 的每个方法上都加注解非常繁琐,所以你可能会尝试把注解直接加在 Controller 上。不过这样做也还是很繁琐。其实在 Spring Boot 中可以通过全局配置一次性解决这个问题——只需要在 SpringMVC 的配置类中重写 addCorsMappings
方法即可,如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("*")
.allowedHeaders("*");
}
}
/**
表示所有方法都可以响应跨域请求,allowedMethods
表示允许通过的请求数,allowedHeaders
则表示允许的请求头。经过这样的配置之后,就不必在每个方法上单独配置。
37.3.3 跨域请求的安全威胁
支持跨域请求的系统会面临潜在的威胁存在,常见的就是 CSRF(Cross-site request forgery,跨站请求伪造)。它是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。详细的讨论见教程相关章节。
37.4 使用Spring Security 配置跨域请求
使用了 Spring Security以后,前文的跨域配置就不会再起作用,因为所有的请求都会被 Spring Security 拦截。此时我们有两种办法开启 Spring Security 对跨域的支持。
37.3.1 开启 Spting Security 对 CORS 的支持
在 Spring Security 的配置中用如下方式开启对于 CORS 的支持:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
... 省略代码 ..
.and()
.cors()
... 省略代码 ..
}
}
只需要执行 cors() 方法即可开启 Spring Security 对 CORS 的支持。
37.2 更加详细的参数配置
下面的代码在 Spring Security 中对 CORS 做更加详细的参数配置:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
http
... 省略代码 ..
.and()
.cors()
.configurationSource(corsConfigurationSource())
... 省略代码 ..
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setMaxAge(Duration.ofHours(1));
source.registerCorsConfiguration("/**",configuration);
return source;
}
}
上面的代码通过 CorsConfigurationSource
实例对跨域信息作出详细配置,例如允许的请求来源、允许的请求方法、允许通过的请求头、探测请求的有效期、需要处理的路径等等。
37.5 访问 OAuth2 资源时支持跨域
如果用户要访问 OAuth2 端点,需要配置一个 CorsFilter。下面是核心配置:
@Configuration
public class GlobalCorsConfiguration {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
然后在 SecurityConfig 中开启跨域支持:
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/oauth/**")
.and()
.csrf().disable().formLogin()
.and()
.cors();
}
}
版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。