什麼是 CORS ?一文搞懂 CORS 跨域原理!
在做 Web 開發時,CORS 跨域是我們經常遇到的問題,這篇文章,我們將一起分析什麼是 CORS?CORS 的原理是什麼?爲什麼需要 CORS?
1. 什麼是 CORS?
CORS,全稱爲 “跨域資源共享”(Cross-Origin Resource Sharing),是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器允許一個網頁從另一個域(不同於該網頁所在的域)請求資源。這樣可以在服務器和客戶端之間進行安全的跨域通信。
當一個網頁向不同源發出請求時,CORS 會通過以下幾個步驟來處理:
-
預檢請求(Preflight Request):對於某些類型的請求(如使用 HTTP 方法 PUT、DELETE,或者請求帶有非簡單頭部),瀏覽器會首先發送一個 OPTIONS 請求,這個請求稱爲 “預檢請求”。服務器收到這個請求後,會返回一個響應頭部,指明實際請求是否被允許。
-
實際請求(Actual Request):如果預檢請求通過,瀏覽器會繼續發送實際的請求。
-
響應頭部(Response Headers):服務器在響應中會包含一些特定的 CORS 頭部,如 Access-Control-Allow-Origin,以指示哪些域名可以訪問資源。
2. 什麼是 Origin?
Origin,翻譯爲 源(域),在 CORS 上下文中 Origin 由三個元素組成:
Origin = 協議 + 域名 + 端口
協議:例如 http:// 或 https:// 域名:例如 www.yuanjava.com 端口:例如 80(默認 HTTP 端口)、443(默認 HTTPs 端口)
只有上述三個元素都匹配時,我們纔會認爲兩個 URL 具有相同的來源,否則,有任何一個不相同都認爲不同源。
3. 同源策略
同源策略(Same-Origin Policy, SOP)是瀏覽器的一種安全機制,用於防止惡意網站通過腳本對其他網站的內容進行訪問。
所謂 “同源”,是指協議、域名和端口都相同。比如,以下 URL 屬於同源地址:
-
https://yuanjava.com/categories 和 https://yuanjava.com/archives
-
https://yuanjava.com:443 和 https://yuanjava.com:443/interview
4. 跨域請求
跨域請求是指從一個域向另一個域發起的 HTTP 請求。
在現代 Web 應用中,跨域請求非常常見,比如,從前端應用向不同的後端 API 服務器請求數據,或從一個 Web 服務請求另一個 Web 服務的資源,因爲,同源策略默認會阻止這些請求,所以需要 CORS 機制來顯式允許跨域訪問。
以下 URL 則被認爲是跨域請求:
-
http://yuanjava.com 和 https://yuanjava.com(協議不同)
-
http://yuanjava.com 和 http://blog.yuanjava.com(域名不同)
-
http://yuanjava.com:80 和 http://yuanjava.com:8080(端口不同)
下圖顯示了 CORS 流的主要參與者:
下圖展示了瀏覽器默認允許同源請求,而跨域請求則被阻止:
5. CORS 工作流程
CORS 通過在 HTTP(s) 請求和響應中使用特定的頭部字段來實現跨域資源共享,具體來說,CORS 分爲兩種類型的請求處理方式:簡單請求和預檢請求。
-
簡單請求:對於某些簡單的 HTTP 請求(如 GET、POST 請求且不包含自定義頭部),瀏覽器會直接發送請求,並在響應中檢查 CORS 頭部。
-
預檢請求:對於複雜請求(如使用 PUT、DELETE 方法,或包含自定義頭部),瀏覽器會首先發送一個 OPTIONS 請求,稱爲預檢請求(Preflight Request),以確定服務器是否允許實際請求。
簡單請求
簡單請求是指滿足以下條件的 HTTP 請求:
-
使用 GET、POST、HEAD 方法
-
請求頭部僅包含以下字段:Accept、Accept-Language、Content-Language、Content-Type(且值爲 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)
對於簡單請求,瀏覽器會直接發送請求並在響應中檢查以下 CORS 頭部:
-
Access-Control-Allow-Origin:指示允許訪問資源的源。
-
Access-Control-Allow-Credentials:指示是否允許發送憑據(如 Cookies)。
-
Access-Control-Expose-Headers:指示哪些頭部可以作爲響應的一部分被訪問。
比如,下面一個示例:
客戶端請求:
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
請求,包含以下頭部字段:
-
Origin:指示請求的源。
-
Access-Control-Request-Method:指示實際請求將使用的方法。
-
Access-Control-Request-Headers:指示實際請求將包含的自定義頭部。
服務器收到預檢請求後,會返回一個響應,包含以下頭部字段以指示是否允許請求:
-
Access-Control-Allow-Origin:表明允許訪問資源的源,可以是具體的源或通配符 *;
-
Access-Control-Allow-Methods:表明允許的方法,如 GET, POST, PUT, DELETE;
-
Access-Control-Allow-Headers:表明允許的自定義頭部;
-
Access-Control-Allow-Credentials:表明是否允許發送憑據(如 Cookies);
-
Access-Control-Expose-Headers:表明哪些頭部可以作爲響應的一部分被訪問;
-
Access-Control-Max-Age:表明預檢請求的結果可以被緩存的時間,單位是秒;
如果預檢請求通過,瀏覽器會繼續發送實際請求。
比如,下面一個示例:
預檢請求:
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