SpringBoot 優雅配置跨域多種方式及Spring Security跨域訪問配置的坑

前言

最近在做項目的時候,基於前後端分離的許可權管理系統,後台使用 Spring Security 作為許可權控制管理, 然後在前端介面訪問時候涉及到跨域,但我怎麼配置跨域也沒有生效,這裡有一個坑,在使用Spring Security時候單獨配置,SpringBoot 跨越還不行,還需要配置Security 跨域才行。

什麼是跨域

跨域是一種瀏覽器同源安全策略,即瀏覽器單方面限制腳本的跨域訪問

在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等標籤以及 Ajax 都可以指向一個資源地址,而所謂的跨域請求就是指:當前發起請求的域與該請求指向的資源所在的域不一樣。這裡的域指的是這樣的一個概念:我們認為若協議 + 域名 + 埠號均相同,那麼就是同域

當前頁面URL和請求的URL首部(埠之前部分)不同則會造成跨域。通俗點講就是兩個URL地址,埠之前部分,只要有一點不同即發生跨域。

例如:

1. 在//a.baidu.com下訪問//a.baidu.com資源會形成協議跨域。

2. 在a.baidu.com下訪問b.baidu.com資源會形成主機跨域。

3. 在a.baidu.com:80下訪問a.baidu.com:8080資源會形成埠跨域。

解決跨域的常見方式

JSONP

由於瀏覽器允許一些帶有src屬性的標籤跨域,常見的有iframe、script、img等,所以JSONP利用script標籤可以實習跨域

前端通過script標籤請求,並在callback中指定返回的包裝實體名稱為jsonp(可以自定義)

<script src="//aaa.com/getusers?callback=jsonp"></script>

後端將返回結果包裝成所需數據格式

jsonp({
    "error":200,
    "message":"請求成功",
    "data":[{
        "username":"張三",
        "age":20
    }]
})

總結:JSONP實現起來很簡單,但是只支援GET請求跨域,存在較大的局限性

CORS

CORS是一個W3C標準,全稱是」跨域資源共享」(Cross-origin resource sharing),允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。

規範中有一組新增的HTTP首部欄位 它通過伺服器增加一個特殊的Header[Access-Control-Allow-Origin]來告訴客戶端跨域的限制,如果瀏覽器支援CORS、並且判斷Origin通過的話,就會允許XMLHttpRequest發起跨域請求

注意:CORS不支援IE8以下版本的瀏覽器

CORS Header屬性 解釋
Access-Control-Allow-Origin 允許//www.xxx.com域(自行設置,這裡只做示例)發起跨域請求
Access-Control-Max-Age 設置在86400秒不需要再發送預校驗請求
Access-Control-Allow-Methods 設置允許跨域請求的方法
Access-Control-Allow-Headers 允許跨域請求包含content-type
Access-Control-Allow-Credentials 設置允許Cookie

SpringBoot解決跨越方式

@CrossOrigin 註解

這種是SpringBoot自帶的註解,使用非常簡單,只需要在對應的介面添加上次註解就行

就表示這個介面支援跨域,其中origins = "*"
表示所有的地址都可以訪問這個介面,也可以寫具體的地址,表示只有這個地址訪問才能訪問到介面

可以註解在類上,和方法上,類上表示此controller所有介面都支援跨域,單個方法上表示只有這個介面支援跨域

這種方法雖然優雅簡單,但是缺點也不小,需要跨域的介面都需要加上這個註解,這對前後端分離的項目是不友好的,需要添加很多次,所以這種方式基本上用的很少

攔截器方式

重寫WebMvcConfigurer的addCorsMappings 方法

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")//項目中的所有介面都支援跨域
                .allowedOriginPatterns("*")//所有地址都可以訪問,也可以配置具體地址
                .allowCredentials(true)
                .allowedMethods("*")//"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
                .maxAge(3600);// 跨域允許時間
    }
}

注意allowedOrigins("*")allowCredentials(true)true時候會出現錯誤需要改成allowedOriginPatterns("*")或者單獨指定介面allowedOrigins("http//www.baidu.com")

@Configuration 表示是配置類,在項目啟動的時候會載入。實現WebMvcConfigurer 介面並重寫addCorsMappings 方法。程式碼比較簡單,也有注釋

Filter過濾器方式

  1. 方式一
@Bean
    public CorsFilter corsFilter() {
         //1.添加CORS配置資訊
        CorsConfiguration config = new CorsConfiguration();
          //放行哪些原始域
          config.addAllowedOrigin("*");
          //是否發送Cookie資訊
          config.setAllowCredentials(true);
          //放行哪些原始域(請求方式)
          config.addAllowedMethod("*");
          //放行哪些原始域(頭部資訊)
          config.addAllowedHeader("*");
          //暴露哪些頭部資訊(因為跨域訪問默認不能獲取全部頭部資訊)
          config.addExposedHeader("*");

        //2.添加映射路徑
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }
  1. 方式二
    基於servler 最原始的配置方式
@Slf4j
@Component
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, client_id, uuid, Authorization");
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        filterChain.doFilter(servletRequest,response);
    }
}

以上三種方法都可以解決問題,最常用的應該是第一種、第二種,控制在自家幾個域名範圍下足以,一般沒必要搞得太細。

這三種配置方式都用了的話,誰生效呢,類似css中樣式,就近原則,懂了吧

Spring Security啟用CORS支援

如果項目中使用了Spring Security 配置了上面跨越方式還不行,需要單獨指定Spring Security跨域

否則跨越不會生效

Spring Security對CORS提供了非常好的支援,只需在配置器中啟用CORS支援,並編寫一 個CORS配置源即可

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // We don't need CSRF for this example
        http.cors().and().csrf().disable()
                // dont authenticate this particular request
                .authorizeRequests().antMatchers("/", "/*.html", "/favicon.ico", "/css/**", "/js/**", "/fonts/**", "/layui/**", "/img/**",
                "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", "/pages/**", "/druid/**",
                "/statics/**", "/login", "/register").permitAll().
                // all other requests need to be authenticated
                        anyRequest().authenticated().and().
                // make sure we use stateless session; session won't be used to
                // store user's state.
                //覆蓋默認登錄
                        exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
                // 基於token,所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);


        // Add a filter to validate the tokens with every request
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
    
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOriginPattern("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }