聊一聊Spring Boot 中跨域場景

寫在前面

跨域問題我相信大多數人都遇見過,這裏我做一個簡單的總結,大體上將跨域問題進行一個簡單的介紹,以及針對SpringBoot進行跨域解決方案的說明。如果覺得寫得好有所收穫,記得點個贊及點個關注哦。

介紹跨域

跨域有個的英文簡稱,叫做CORS,其全稱叫做跨域資源共享(CORS) ,是一種機制。跨域的基本原理就是使用額外的 HTTP 頭來告訴瀏覽器,讓運行在一個 origin (domain) 上的 Web 應用被准許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器「不同的域、協議或端口」請求一個資源時,資源會發起一個「跨域 HTTP 請求」。

這裏要強調一下的是,很多人對跨域有一種誤解,以爲這是前端的事,和後端沒關係,其實不是這樣的,說到跨域,就不得不說說瀏覽器的同源策略。同源策略是由 Netscape 提出的一個著名的安全策略,它是瀏覽器最核心也最基本的安全功能,現在所有支持 JavaScript 的瀏覽器都會使用這個策略。所謂同源是指協議、域名以及端口要相同。換而言之,如果協議、域名或者端口不相同,那麼應用與服務之間的請求就是跨域請求,這個時候就需要進行特殊的處理,才能通過瀏覽器的安全策略。

同源策略是基於安全方面的考慮提出來的,這個策略本身沒問題,但是我們在實際開發中,由於各種原因又經常有跨域的需求,傳統的跨域方案是 JSONP,JSONP 雖然能解決跨域但是有一個很大的侷限性,那就是隻支持 GET 請求,不支持其他類型的請求,在 RESTful 時代這幾乎就沒什麼用。而這裏說的 CORS(跨域源資源共享)是一個 W3C 標準,它是一份瀏覽器技術的規範,提供了 Web 服務從不同網域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,這是 JSONP 模式的現代版。在 Spring 框架中,對於 CORS 也提供了相應的解決方案,在 Spring Boot 中,這一方案得倒了簡化,無論是單純的跨域,還是結合 Spring Security 之後的跨域,都變得非常容易了。

事前準備

首先創建兩個普通的 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 請求,如下:

<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 則表示允許的請求頭。經過這樣的配置之後,就不必在每個方法上單獨配置跨域了。

SpringSecurity中解決跨域問題

如果使用了 Spring Security,上面的跨域配置會失效,因爲請求被 Spring Security 攔截了。當引入了 Spring Security 的時候,我們有兩種辦法開啓 Spring Security 對跨域的支持。

方式一

方式一就是在前面的基礎上,添加 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 的支持。

方式二

方式二則是去除前面的跨域配置,直接在 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 實例對跨域信息作出詳細配置,例如允許的請求來源、允許的請求方法、允許通過的請求頭、探測請求的有效期、需要處理的路徑等等。使用這種方式就可以去掉前面的跨域配置了。

OAuth2跨域處理

還有一種情況就是 OAuth2 允許跨域,如果用戶要訪問 OAuth2 端點,例如/oauth/token ,出現了跨域該怎麼配置呢?這個其實很簡單,我們只要在請求中攜帶一個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();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章