什麼是 CORS ?一文搞懂 CORS 跨域原理!

在做 Web 開發時,CORS 跨域是我們經常遇到的問題,這篇文章,我們將一起分析什麼是 CORS?CORS 的原理是什麼?爲什麼需要 CORS?

1. 什麼是 CORS?

CORS,全稱爲 “跨域資源共享”(Cross-Origin Resource Sharing),是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器允許一個網頁從另一個域(不同於該網頁所在的域)請求資源。這樣可以在服務器和客戶端之間進行安全的跨域通信。

當一個網頁向不同源發出請求時,CORS 會通過以下幾個步驟來處理:

2. 什麼是 Origin?

Origin,翻譯爲 源(域),在 CORS 上下文中 Origin 由三個元素組成:

Origin = 協議 + 域名 + 端口

協議:例如 http:// 或 https://          域名:例如 www.yuanjava.com         端口:例如 80(默認 HTTP 端口)、443(默認 HTTPs 端口)

只有上述三個元素都匹配時,我們纔會認爲兩個 URL 具有相同的來源,否則,有任何一個不相同都認爲不同源。

3. 同源策略

同源策略(Same-Origin Policy, SOP)是瀏覽器的一種安全機制,用於防止惡意網站通過腳本對其他網站的內容進行訪問。

所謂 “同源”,是指協議、域名和端口都相同。比如,以下 URL 屬於同源地址:

4. 跨域請求

跨域請求是指從一個域向另一個域發起的 HTTP 請求。

在現代 Web 應用中,跨域請求非常常見,比如,從前端應用向不同的後端 API 服務器請求數據,或從一個 Web 服務請求另一個 Web 服務的資源,因爲,同源策略默認會阻止這些請求,所以需要 CORS 機制來顯式允許跨域訪問。

以下 URL 則被認爲是跨域請求:

下圖顯示了 CORS 流的主要參與者:

下圖展示了瀏覽器默認允許同源請求,而跨域請求則被阻止:

5. CORS 工作流程

CORS 通過在 HTTP(s) 請求和響應中使用特定的頭部字段來實現跨域資源共享,具體來說,CORS 分爲兩種類型的請求處理方式:簡單請求和預檢請求。

簡單請求

簡單請求是指滿足以下條件的 HTTP 請求:

對於簡單請求,瀏覽器會直接發送請求並在響應中檢查以下 CORS 頭部:

比如,下面一個示例:

客戶端請求:

GET /api/data HTTP/1.1
Host: www.yuanjava.com
Origin: https://yuanjava.com

服務器響應:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Content-Type: application/json

{"message": "Hello, CORS!"}

預檢請求

對於複雜請求,瀏覽器會首先發送一個 OPTIONS 請求,包含以下頭部字段:

服務器收到預檢請求後,會返回一個響應,包含以下頭部字段以指示是否允許請求:

如果預檢請求通過,瀏覽器會繼續發送實際請求。

比如,下面一個示例:

預檢請求:

OPTIONS /api/data HTTP/1.1
Host: api.yuanjava.com
Origin: https://yuanjava.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type

預檢響應:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yuanjava.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600

實際請求

PUT /api/data HTTP/1.1
Host: api.yuanjava.com
Origin: https://yuanjava.com
Content-Type: application/json

{"data": "example"}

實際響應:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Content-Type: application/json

{"message": "Data updated"}

6. 如何實現 CORS?

客戶端處理

客戶端可以向遠程服務器發送簽名請求。

如下示例代碼:在 CORS 請求中以 Authorization 標頭的形式發送憑據:

function sendAuthRequestToCrossOrigin() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          document.getElementById("demo").innerHTML = this.responseText;
        }
    };
    xhr.open('GET', "https://yuanjava:8000/categories", true);
    xhr.setRequestHeader('Authorization', 'Bearer rtikkjhgffw456tfdd');
    xhr.withCredentials = true;
    xhr.send();
}

服務器端處理

方法 1:直接採用 SpringBoot 的註解 @CrossOrigin

如下示例代碼如下,可以把 @CrossOrigin 加在每個 Controller 上,也可以加在它們的公共父類上:

@CrossOrigin
@RestController
public class TestController extends BaseController {

    //  其他邏輯
}

方法 2: 採用過濾器(filter)的方式

如下示例代碼:增加一個 CORSFilter 類,並實現 Filter 接口即可。

 @Component
public class CORSFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) response;
        res.addHeader("Access-Control-Allow-Credentials", "true");
        res.addHeader("Access-Control-Allow-Origin", "*");
        res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN");
        if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
            response.getWriter().println("ok");
            return;
        }
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}

方法 3: 配置 Configuration

如下示例代碼:增加一個配置類繼承 WebMvcConfigurerAdapter 或者實現 WebMvcConfigurer 接口,項目啓動時,會自動讀取配置。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
    static final String ORIGINS[] = new String[]{"GET", "POST", "PUT", "DELETE"};

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods(ORIGINS).maxAge(3600);
    }
}

另外,在服務器,可以通過設置響應頭部來細粒度配置 CORS,具體的如下:

1. 允許所有源訪問

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *

2. 允許特定源訪問

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com

3. 允許憑據請求訪問

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Access-Control-Allow-Credentials: true

4. 允許特定方法和頭部

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization

5. 設置預檢請求的緩存時間

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600  // 3600秒

通常來說,在服務器解決 CORS 是一種比較常見和徹底的方式,我們可以在服務器靈活的設置允許跨域訪問的域名或者地址。

7. 常見問題及解決方案

問題 1:No 'Access-Control-Allow-Origin' header is present on the requested resource

問題描述:當瀏覽器發起跨域請求時,未在響應中找到 Access-Control-Allow-Origin 頭部。

解決方案:確保服務器端正確設置了 Access-Control-Allow-Origin 頭部。例如:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com

問題 2:The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is'include'

問題描述:當請求包含憑據時,Access-Control-Allow-Origin 頭部不能設置爲通配符 *。

解決方案:明確指定允許的源,並確保設置了 Access-Control-Allow-Credentials 頭部。例如:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Access-Control-Allow-Credentials: true

問題 3:CORS preflight channel did not succeed

問題描述:預檢請求失敗,可能是由於服務器未正確處理 OPTIONS 請求。

解決方案:確保服務器正確處理 OPTIONS 請求並返回相應的 CORS 頭部。例如,在 Node.js/Express 中:

app.options('/api/data', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'https://yuanjava.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Credentials', 'true');
  res.sendStatus(204);
});

8. 總結

CORS 是現代 Web 開發中不可或缺的機制,它允許 Web 應用在安全的前提下進行跨域資源請求,通過理解 CORS 的工作原理和配置方法,可以幫助我們有效地解決跨域請求的問題。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/FpQCqNN41lBGqJXUAYOFCA