首頁>技術>

跨域這個問題鬆哥之前寫過文章,但是最近收到小夥伴們的一些問題,讓我發現之前的總結不夠全面,因此打算再寫一篇文章,來和大家分享一下 Spring Boot 中的跨域問題。

這次我把 Spring Boot 中的跨域問題分為了三個場景:

普通跨域Spring Security 跨域OAuth2 跨域

分為三種並非多此一舉,主要是因為這三種場景的配置都不太一樣,而這三種場景又都是非常常見的場景,所以這裡和大家再來專門分享下。

1.什麼是跨域

很多人對跨域有一種誤解,以為這是前端的事,和後端沒關係,其實不是這樣的,說到跨域,就不得不說說瀏覽器的同源策略。

同源策略是由 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,如下:

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

在 consumer 的 resources/static 目錄下建立一個 html 檔案,傳送一個簡單的 ajax 請求,如下:

<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 註解配置某一個方法接受某一個域的請求,如下:

@RestControllerpublic 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 方法即可,如下:

@Configurationpublic 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 攻擊的具體介紹和防禦辦法,大家可以參考鬆哥之前的文章,這裡就不重複介紹了:

鬆哥手把手教你在 SpringBoot 中防禦 CSRF 攻擊!so easy!要學就學透徹!Spring Security 中 CSRF 防禦原始碼解析3.SpringSecurity

如果使用了 Spring Security,上面的跨域配置會失效,因為請求被 Spring Security 攔截了。

當引入了 Spring Security 的時候,我們有兩種辦法開啟 Spring Security 對跨域的支援。

3.1 方式一

方式一就是在上一小節的基礎上,新增 Spring Security 對於 CORS 的支援,只需要新增如下配置即可:

@Configurationpublic 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 中做全域性配置,如下:

@Configurationpublic 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,大家可以參考該篇文章,我這裡就把核心配置類列出來:

@Configurationpublic 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 到呢?如果覺得有收穫,記得點個在看鼓勵下鬆哥哦~

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 「乾貨」9個最熱門React PC端元件庫|UI框架