Spring Boot 通過CORS實現跨域

DOONDO發表於2020-09-01

同源策略

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

同源策略是由 Netscape 提出的一個著名的安全策略,它是瀏覽器最核心也最基本的安全功能,現在所有支援 JavaScript 的瀏覽器都會使用這個策略。所謂同源是指協議、域名以及埠要相同。同源策略是基於安全方面的考慮提出來的,這個策略本身沒問題,但是我們在實際開發中,由於各種原因又經常有跨域的需求,傳統的跨域方案是 JSONP,JSONP 雖然能解決跨域但是有一個很大的侷限性,那就是隻支援 GET 請求,不支援其他型別的請求,而今天我們說的 CORS(跨域源資源共享)(CORS,Cross-origin resource sharing)是一個 W3C 標準,它是一份瀏覽器技術的規範提供了 Web 服務從不同網域傳來沙盒指令碼的方法,以避開瀏覽器的同源策略,這是 JSONP 模式的現代版。

實踐

首先,我們新建兩個工程:

新建工程一:cors1 project

勾選Web模組 因為我們等下需要通過web介面進行測試 點選Finish完成構建

在cors1 中 我們新建一個HelloController,寫上一個測試介面:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello cors1";
    }
}

新建工程二:cors2 project


對應的勾選Web模組進行構建。

在cors2的static目錄下,建立Index.html,並編寫一個GET請求按鈕,發起Ajax請求(前提:static目錄下有jquery.js)
請求cors1 工程的 localhost:8080/hello介面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery3.3.1.js"></script>
</head>
<body>
<div id="app"></div>
<input type="button" value="GET" onclick="getData()">
<input type="button" value="PUT" onclick="putData()">
<script>
    function getData() {
        $.get("http://localhost:8080/hello",function (msg) {
            $("#app").html(msg);
        })    
    }
</script>
</body>
</html>

並設定cors2 工程的專案埠:

server.port = 8081

分別啟動cors1 和 cors2

訪問 localhost:8081/index.html 點選按鈕,傳送請求:

訪問報錯,目前不支援跨域,驗證了上面說的 同源策略

解決方案一:

1.在Controller類 或 介面方法上,使用 註解 @CrossOrigin 指定允許哪個域伺服器訪問

@RestController
public class HelloController {

    @GetMapping("/hello")
    @CrossOrigin(origins = "http://localhost:8081")
    public String hello(){
        return "hello cors1";
    }
}

重啟 Cors1 專案,點選傳送請求:

訪問成功,瀏覽器控制檯也沒有報錯

注意:
這種方式有一個弊端,就是我們需要在對外開放的每個介面或者類上都要寫一遍,大大增加了開發的重複性和繁瑣性

解決方案二

Spring Boot 中,可以通過全域性配置一次性解決這個問題,全域性配置只需要在 SpringMVC 的配置類中重寫 **addCorsMappings **方法即可

package org.taoguoguo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author taoguoguo
 * @description WebMvcConfig
 * @website https://www.cnblogs.com/doondo
 * @create 2020-09-01 21:31
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 跨域全域性配置
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //addMapping 允許哪些介面跨域
        //allowedOrigins 允許哪個域伺服器訪問
        //allowedHeaders 允許通過的請求頭
        //maxAge 伺服器在傳送一些請求(例如put)會先發一個探測請求,避免每次都需要傳送探測請求 可以設定有效期
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:8081")
                .allowedHeaders("*")
                .allowedMethods("*")
                .maxAge(30*1000);
    }
}

/** 表示本應用的所有方法都會去處理跨域請求,allowedMethods 表示允許通過的請求數,allowedHeaders 則表示允許的請求頭。經過這樣的配置之後,就不必在每個方法上單獨配置跨域了

現在再去頁面訪問,也是沒有問題的

探測請求

我們在跨域設定中

maxAge 伺服器在傳送一些請求(例如put)會先發一個探測請求,避免每次都需要傳送探測請求 可以設定有效期

先舉一個例子:
我們在Cors1中新增一個put請求

@RestController
public class HelloController {

    @GetMapping("/hello")
    @CrossOrigin(origins = "http://localhost:8081")
    public String hello(){
        return "hello cors1";
    }

    @PutMapping("/doput")
    public String doput(){
        return "doput";
    }
}

在cors2中增加put請求傳送按鈕

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="jquery3.3.1.js"></script>
</head>
<body>
<div id="app"></div>
<input type="button" value="GET" onclick="getData()">
<input type="button" value="PUT" onclick="putData()">
<script>
    function getData() {
        $.get("http://localhost:8080/hello",function (msg) {
            $("#app").html(msg);
        })
    }
    
    function putData() {
        $.ajax({
            type:'put',
            url: 'http://localhost:8080/doput',
            success:function (msg) {
                $("#app").html(msg);
            }
        })
        
    }
</script>
</body>
</html>

啟動cors1 cors2 傳送put請求:

我們看到請求一次 瀏覽器會傳送兩次請求,其中Request Methods 為 options 的即為探測請求
因為我們設定了探測請求的有效期,因此當我們再次傳送時,瀏覽器只會傳送一次請求

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

假如一家銀行用以執行轉賬操作的URL地址如下:**http://icbc.com/aa?bb=cc**,那麼,一個惡意攻擊者可以在另一個網站上放置如下程式碼:**<img src="http://icbc.com/aa?bb=cc">**,如果使用者訪問了惡意站點,而她之前剛訪問過銀行不久,登入資訊尚未過期,那麼她就會遭受損失。

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

相關文章