天天看點

Springboot——解決跨域問題關于跨域介紹情景複現解決方案存在的問題

關于跨域介紹

在前後分離的架構下,跨域問題難免會遇見比如,站點 http://domain-a.com 的某 HTML 頁面通過 的 src 請求 http://domain-b.com/image.jpg。網絡上的許多頁面都會加載來自不同域的CSS樣式表,圖像和腳本等資源。

出于安全原因,浏覽器限制從腳本内發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源政策。 這意味着使用這些API的Web應用程式隻能從加載應用程式的同一個域請求HTTP資源,除非使用CORS頭檔案。

跨域的展現,在于它的域名不同或者端口不同

CORS(跨域源資源共享)(CORS,Cross-origin resource sharing)

CORS是一個 W3C 标準,它是一份浏覽器技術的規範,提供了 Web 服務從不同網域傳來沙盒腳本的方法,以避開浏覽器的同源政策,這是 JSONP 模式的現代版。

情景複現

建兩個普通的 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",maxAge = 3600)  //origin="*"代表所有域名都可通路  //maxAge飛行前響應的緩存持續時間的最大年齡,簡單來說就是Cookie的有效期 機關為秒
//若maxAge是負數,則代表為臨時Cookie,不會被持久化,Cookie資訊儲存在浏覽器記憶體中,浏覽器關閉Cookie就消失
    @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 也能拿到資料。

此時浏覽器請求網絡控制台,可以看到響應頭中多了如下資訊:

Springboot——解決跨域問題關于跨域介紹情景複現解決方案存在的問題

這個表示服務端願意接收來自 http://localhost:8081 的請求,拿到這個資訊後,浏覽器就不會再去限制本次請求的跨域了。

全局配置

全局配置隻需要在 SpringMVC 的配置類中重寫 addCorsMappings 方法即可,如下:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        .allowedOrigins("http://localhost:8081")
        .allowedMethods("*") //.allowedMethods("PUT", "DELETE","POST","GET")
        .allowedHeaders("*").maxAge(3600);
    }
}                

/** 表示本應用的所有方法都會去處理跨域請求

allowedMethods 表示允許通過的請求數

allowedHeaders 則表示允許的請求頭

過濾器解決跨域

可以把之前的三個方法的注解或者@Configuration都去掉

@Configuration
public class CrosFilter {
    @Bean
    CorsFilter getCorsFilter(){
        CorsConfiguration cors = new CorsConfiguration();
        cors.setAllowCredentials(true);
        cors.addAllowedMethod("*");
        cors.addAllowedOrigin("*");
        cors.setMaxAge(3600L);
        cors.addAllowedHeader("*");
        cors.applyPermitDefaultValues();

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",cors);

        CorsFilter corsFilter = new CorsFilter(source);
        return corsFilter;
    }
}                

存在的問題

了解了整個 CORS 的工作過程之後,我們通過 Ajax 發送跨域請求,雖然使用者體驗提高了,但是也有潛在的威脅存在,常見的就是 CSRF(Cross-site request forgery)跨站請求僞造。跨站請求僞造也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF,是一種挾制使用者在目前已登入的 Web 應用程式上執行非本意的操作的攻擊方法,舉個例子:

假如一家銀行用以運作轉賬操作的URL位址如下:http://icbc.com/aa?bb=cc,那麼,一個惡意攻擊者可以在另一個網站上放置如下代碼:
Springboot——解決跨域問題關于跨域介紹情景複現解決方案存在的問題
,如果使用者通路了惡意站點,而她之前剛通路過銀行不久,登入資訊尚未過期,那麼她就會遭受損失。

基于此,浏覽器在實際操作中,會對請求進行分類,分為簡單請求,預先請求,帶憑證的請求等,預先請求會首先發送一個 options 探測請求,和浏覽器進行協商是否接受請求。預設情況下跨域請求是不需要憑證的,但是服務端可以配置要求用戶端提供憑證,這樣就可以有效避免 csrf 攻擊。