Spring Boot 中三种跨域场景总结

@[toc]
跨域这个问题松哥以前写过文章,可是最近收到小伙伴们的一些问题,让我发现以前的总结不够全面,所以打算再写一篇文章,来和你们分享一下 Spring Boot 中的跨域问题。html

此次我把 Spring Boot 中的跨域问题分为了三个场景:前端

  • 普通跨域
  • Spring Security 跨域
  • OAuth2 跨域

分为三种并不是画蛇添足,主要是由于这三种场景的配置都不太同样,而这三种场景又都是很是常见的场景,因此这里和你们再来专门分享下。java

1.什么是跨域

不少人对跨域有一种误解,觉得这是前端的事,和后端不要紧,其实不是这样的,说到跨域,就不得不说说浏览器的同源策略。ajax

同源策略是由 Netscape 提出的一个著名的安全策略,它是浏览器最核心也最基本的安全功能,如今全部支持 JavaScript 的浏览器都会使用这个策略。所谓同源是指协议、域名以及端口要相同。后端

同源策略是基于安全方面的考虑提出来的,这个策略自己没问题,可是咱们在实际开发中,因为各类缘由又常常有跨域的需求,传统的跨域方案是 JSONP,JSONP 虽然能解决跨域可是有一个很大的局限性,那就是只支持 GET 请求,不支持其余类型的请求,在 RESTful 时代这几乎就没什么用。跨域

而今天咱们说的 CORS(跨域源资源共享)(CORS,Cross-origin resource sharing)是一个 W3C 标准,它是一份浏览器技术的规范,提供了 Web 服务从不一样网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是 JSONP 模式的现代版。浏览器

在 Spring 框架中,对于 CORS 也提供了相应的解决方案,在 Spring Boot 中,这一方案得倒了简化,不管是单纯的跨域,仍是结合 Spring Security 以后的跨域,都变得很是容易了。安全

2.解决方案

首先建立两个普通的 Spring Boot 项目,这个就不用我多说,第一个命名为 provider 提供服务,第二个命名为 consumer 消费服务,第一个配置端口为 8080,第二个配置配置为 8081,而后在 provider 上提供两个 hello 接口,一个 get,一个 post,以下:网络

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    @PostMapping("/hello")
    public String hello2() {
        return "post hello";
    }
}

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

<div id="app"></div>
<input type="button" onclick="btnClick()" value="get_button">
<input type="button" onclick="btnClick2()" value="post_button">
<script>
    function btnClick() {
        $.get('http://localhost:8080/hello', function (msg) {
            $("#app").html(msg);
        });
    }

    function btnClick2() {
        $.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.

能够看到,因为同源策略的限制,请求没法发送成功。

使用 CORS 能够在前端代码不作任何修改的状况下,实现跨域,那么接下来看看在 provider 中如何配置。首先能够经过 @CrossOrigin 注解配置某一个方法接受某一个域的请求,以下:

@RestController
public class HelloController {
    @CrossOrigin(value = "http://localhost:8081")
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @CrossOrigin(value = "http://localhost:8081")
    @PostMapping("/hello")
    public String hello2() {
        return "post hello";
    }
}

这个注解表示这两个接口接受来自 http://localhost:8081 地址的请求,配置完成后,重启 provider ,再次发送请求,浏览器控制台就不会报错了,consumer 也能拿到数据了。

此时观察浏览器请求网络控制台,能够看到响应头中多了以下信息:

这个表示服务端愿意接收来自 http://localhost:8081 的请求,拿到这个信息后,浏览器就不会再去限制本次请求的跨域了。

provider 上,每个方法上都去加注解未免太麻烦了,有的小伙伴想到能够讲注解直接加在 Controller 上,不过每一个 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 则表示容许的请求头。通过这样的配置以后,就没必要在每一个方法上单独配置跨域了。

2.1 存在的问题

了解了整个 CORS 的工做过程以后,咱们经过 Ajax 发送跨域请求,虽然用户体验提升了,可是也有潜在的威胁存在,常见的就是 CSRF(Cross-site request forgery)跨站请求伪造。跨站请求伪造也被称为 one-click attack 或者 session riding,一般缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登陆的 Web 应用程序上执行非本意的操做的攻击方法。

关于 CSRF 攻击的具体介绍和防护办法,你们能够参考松哥以前的文章,这里就不重复介绍了:

3.SpringSecurity

若是使用了 Spring Security,上面的跨域配置会失效,由于请求被 Spring Security 拦截了。

当引入了 Spring Security 的时候,咱们有两种办法开启 Spring Security 对跨域的支持。

3.1 方式一

方式一就是在上一小节的基础上,添加 Spring Security 对于 CORS 的支持,只须要添加以下配置便可:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .httpBasic()
                .and()
                .cors()
                .and()
                .csrf()
                .disable();
    }
}

一个 .cors 就开启了 Spring Security 对 CORS 的支持。

3.2 方式二

方式二则是去除第二小节的跨域配置,直接在 Spring Security 中作全局配置,以下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .httpBasic()
                .and()
                .cors()
                .configurationSource(corsConfigurationSource())
                .and()
                .csrf()
                .disable();
    }
    @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 实例对跨域信息做出详细配置,例如容许的请求来源、容许的请求方法、容许经过的请求头、探测请求的有效期、须要处理的路径等等。

使用这种方式就能够去掉第二小节的跨域配置了。

4.OAuth2

还有一种状况就是 OAuth2 容许跨域,若是用户要访问 OAuth2 端点,例如 /oauth/token ,出现了跨域该怎么配置呢?

这个解决方案松哥在以前的 【用 Swagger 测试接口,怎么在请求头中携带 Token?】 一文中已经有过介绍,主要是配置一个 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();
    }
}

5.小结

好啦,今天主要和小伙伴们总结了一下 Spring Boot 中三种跨域的场景,不知道你们有没有 GET 到呢?若是以为有收获,记得点个在看鼓励下松哥哦~

相关文章
相关标签/搜索