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 到呢?若是以爲有收穫,記得點個在看鼓勵下鬆哥哦~

相關文章
相關標籤/搜索