SpringBoot中通過CORS解決跨域問題(實戰+剖析)

一、同源策略

前言

在開始實際操作之前,先來聊聊什麼是同源策略?許多人都對跨域有一定的誤解,認爲這是前端工程師的問題,和後端沒說什麼關係,其實並不是的,說到跨域,就不得不說一下瀏覽器的同源策略。

簡介

同源策略是由美國Netscape(網景)公司提出的一個著名的安全策略,它是目前市面上的瀏覽器最核心也是最基本的安全功能,現在所有支持JavaScript的瀏覽器都會使用這個策略。所謂同源是指協議、域名、端口都要相同。同源策略是基於安全層面而提出來了,其實這個策略本身並沒有什麼問題,但是開發者在開發中由於各種原因會存在跨域的需求。例如:一個大型項目由多個公司來完成,不同公司開發的項目域名和端口一般不同,但是各個公司之間又有數據交互的需求,這時就會存在跨域的需求。

歷程

傳統的跨域解決方案是JSONP,JSONP雖然能解決跨域,但是它有一個很大的弊端,就是它只支持GET請求,不支持其他類型的請求,但是我們平時常用的還有POST請求,那這種解決方案肯定不行。

而今天我們說的CORS跨域源資源共享)(CORS,Cross-origin resource sharing)是一個W3C標準,它是一份瀏覽器技術的規範,提供了Web服務從不同網域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,這是JSONP模式的現代版。

在Spring框架中,對於CORS也提供瞭解決方案,今天來說說SpringBoot中如何實現CORS。

二、舉例

第一步】先創建兩個SpringBoot項目,一個命名爲cors1提供服務,另一個命名爲cors2消費服務,第一個默認端口8080即可,第二配置端口爲8081,然後在cors1上新建一個Controller,並提供一個get接口,如下:

【cors1 -> HelloController.java】

@RestController
public class HelloController {

    @GetMapping("/get")
    public String getData() {
        return "hello get cors";
    }
}

【cors2 -> application.properties】

server.port=8081

在cors2項目下的resources/static目錄創建一個index.html文件,發送一個簡單的get請求,如下:

【cors2 -> index.html】

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

注:記得引入jquery文件。

然後,分別啓動兩個項目,打開瀏覽器訪問http://localhost:8081/index.html,按F12打開瀏覽器控制檯,點擊GET按鈕,注意觀察紅色提示,如下:

Access to XMLHttpRequest at 'http://localhost:8080/doGet' from origin 'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

可以看到,由於同源策略,所以該請求無法發送成功。

使用CORS可以在前端代碼不做任何修改的前提下實現跨域,接下來我們應該在cors1項目中如何進行配置?在GET請求的方法上,添加@CrossOrigin註解,配置某一個方法接收某一個域的請求,如下:

@RestController
public class HelloController {

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

添加了這個註解的接口,表示接受來自http://localhost:8081地址的請求,重啓cors1項目,打開瀏覽器訪問,這次不會出現前面的錯誤了,cors2也能拿到數據了,如下:
在這裏插入圖片描述
這個表示服務端允許接收來自http://localhost:8081的請求,拿到該信息後,瀏覽器就不會再去限制本次請求的跨域了。

問題一

在cors1項目中,如果每個請求對應一個接口,那每個方法上都要添加@CrossOrigin註解,那太麻煩了吧???

方法一

但是,在SpringBoot中,可以全局配置一次性來解決這個問題,那就是把這個@CrossOrigin註解放到這個類上即可,如下:

@RestController
@CrossOrigin(origins = "http://localhost:8081")
public class HelloController {

    @GetMapping("/doGet")
    public String getData() {
        return "hello get cors";
    }
}

爲啥這個註解可以加到類上呢?你爲什麼不早說?

@CrossOrigin源碼解讀

下面,我們來看看源碼,我們進入這個@CrossOrigin註解中看看,如下:
在這裏插入圖片描述
看到上面箭頭沒有,這就是爲啥,說明這個註解在類和方法上都能生效。

問題二

在cors1項目中,如果每個請求都對應一個類中的某個方法呢?那我是不是每個類都得添加@CrossOrigin註解,也很麻煩啊???

方法二

其實,可以用一個配置類來解決這個問題,這個全局配置只需要在配置類中重寫addCorsMappings方法即可,如下:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("http://localhost:8081")
                .allowedHeaders("*")
                .allowedMethods("*")
                .maxAge(30*1000);
    }
}

/**表示本應用的的所有方法都會去處理跨域請求,allowedOrigins表示允許處理跨域請求的協議、域名、端口,allowedHeaders表示允許的請求頭,allowedMethods表示允許的請求類型(如:GET、POST、PUT等),maxAge表示有效時間(單位:ms,如:發送PUT請求時,第一次發送PUT請求會發送兩次請求,先發送一次探測請求,這時maxAge方法就有作用了,如果在有效時間內完成,會再發一次正式的PUT請求)。

小結

綜上所述,經過這麼一次全局性的配置,就不需要在每個類,甚至每個方法上單獨進行配置了。

三、存在的問題

瞭解整個CORS工作過程後,我們通過Ajax發送跨域請求,雖然使得用戶體驗提高了,但同時也伴隨着其他的風險,比較常見的就是:CSRF(Cross-site request forgery)跨站請求僞造。跨站請求僞造也被稱爲one-click attack 或者 session riding,通常縮寫爲CSRF或者XSRF,是一種挾制用戶在當前已登錄的Web應用程序上執行非本意的操作的攻擊方法,舉例:

假如一家銀行用以運行轉賬操作的URL地址如下:https://icbc.com/accounts?name=xxx,那麼,一個惡意攻擊者可以在另一個網站上放置如下代碼:<imgsrc=“https://icbc.com/accounts?name=xxx”>,如果用戶無意中訪問了惡意站點,而她之前剛訪問過銀行不久,登錄信息尚未過期,那麼她就會遭受損失。

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

四、源碼地址

cors1源碼地址:cors1
cors2源碼地址:cors2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章