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:

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String hello() {
  5. return "This's GetMapping hello";
  6. }
  7. @PostMapping("/hello")
  8. public String hello2() {
  9. return "This's PostMapping hello";
  10. }
  11. }

在 consumer 的 resources/static 目录下创建一个 html 文件,发送一个简单的 ajax 请求,如下:

  1. <div id="app"></div>
  2. <input type="button" onclick="getButtonClick()" value="get_button">
  3. <input type="button" onclick="postButtonClick()" value="post_button">
  4. <script>
  5. function getButtonClick() {
  6. $.get('http://localhost:8080/hello', function (msg) {
  7. $("#app").html(msg);
  8. });
  9. }
  10. function postButtonClick() {
  11. $.post('http://localhost:8080/hello', function (msg) {
  12. $("#app").html(msg);
  13. });
  14. }
  15. </script>

然后分别启动两个项目,发送请求按钮,观察浏览器控制台如下:

  1. 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 注解配置某一个方法接受 另一个域的请求,如下:

  1. @RestController
  2. public class HelloController {
  3. @CrossOrigin(value = "http://localhost:8081")
  4. @GetMapping("/hello")
  5. public String hello() {
  6. return "This's GetMapping hello";
  7. }
  8. @CrossOrigin(value = "http://localhost:8081")
  9. @PostMapping("/hello")
  10. public String hello2() {
  11. return "This's PostMapping hello;
  12. }
  13. }

这个注解表示这两个接口接受来自 http://localhost:8081 地址的请求,配置完成后,重启 provider ,再次发送请求,consumer 能够拿到数据。

此时观察浏览器请求网络控制台,可以看到响应头中多了如下信息:
image.png
这个表示服务端愿意接收来自 http://localhost:8081 的请求,拿到这个信息后,浏览器就不会再去限制本次请求的跨域了。

37.3.2 全局开启支持跨越请求

显然在 provider 的每个方法上都加注解非常繁琐,所以你可能会尝试把注解直接加在 Controller 上。不过这样做也还是很繁琐。其实在 Spring Boot 中可以通过全局配置一次性解决这个问题——只需要在 SpringMVC 的配置类中重写 addCorsMappings 方法即可,如下:

  1. @Configuration
  2. public class WebMvcConfig implements WebMvcConfigurer {
  3. @Override
  4. public void addCorsMappings(CorsRegistry registry) {
  5. registry.addMapping("/**")
  6. .allowedOrigins("http://localhost:8081")
  7. .allowedMethods("*")
  8. .allowedHeaders("*");
  9. }
  10. }

/** 表示所有方法都可以响应跨域请求,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 的支持:

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http
  6. ... 省略代码 ..
  7. .and()
  8. .cors()
  9. ... 省略代码 ..
  10. }
  11. }

只需要执行 cors() 方法即可开启 Spring Security 对 CORS 的支持。

37.2 更加详细的参数配置

下面的代码在 Spring Security 中对 CORS 做更加详细的参数配置:

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http
  6. http
  7. ... 省略代码 ..
  8. .and()
  9. .cors()
  10. .configurationSource(corsConfigurationSource())
  11. ... 省略代码 ..
  12. }
  13. @Bean
  14. CorsConfigurationSource corsConfigurationSource() {
  15. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  16. CorsConfiguration configuration = new CorsConfiguration();
  17. configuration.setAllowCredentials(true);
  18. configuration.setAllowedOrigins(Arrays.asList("*"));
  19. configuration.setAllowedMethods(Arrays.asList("*"));
  20. configuration.setAllowedHeaders(Arrays.asList("*"));
  21. configuration.setMaxAge(Duration.ofHours(1));
  22. source.registerCorsConfiguration("/**",configuration);
  23. return source;
  24. }
  25. }

上面的代码通过 CorsConfigurationSource 实例对跨域信息作出详细配置,例如允许的请求来源、允许的请求方法、允许通过的请求头、探测请求的有效期、需要处理的路径等等。

使用这种方式就可以去掉第3.3.2中的跨域配置了。

37.5 访问 OAuth2 资源时支持跨域

如果用户要访问 OAuth2 端点,需要配置一个 CorsFilter。下面是核心配置:

  1. @Configuration
  2. public class GlobalCorsConfiguration {
  3. @Bean
  4. public CorsFilter corsFilter() {
  5. CorsConfiguration corsConfiguration = new CorsConfiguration();
  6. corsConfiguration.setAllowCredentials(true);
  7. corsConfiguration.addAllowedOrigin("*");
  8. corsConfiguration.addAllowedHeader("*");
  9. corsConfiguration.addAllowedMethod("*");
  10. UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
  11. urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
  12. return new CorsFilter(urlBasedCorsConfigurationSource);
  13. }
  14. }

然后在 SecurityConfig 中开启跨域支持:

  1. @Configuration
  2. @Order(Ordered.HIGHEST_PRECEDENCE)
  3. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  4. ...
  5. ...
  6. @Override
  7. protected void configure(HttpSecurity http) throws Exception {
  8. http
  9. .requestMatchers().antMatchers(HttpMethod.OPTIONS, "/oauth/**")
  10. .and()
  11. .csrf().disable().formLogin()
  12. .and()
  13. .cors();
  14. }
  15. }

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。