瀏覽器的同源策略阻止讀取來自不同來源的資源。這種機制阻止惡意站點讀取另一個站點的數(shù)據(jù),但它也阻止合法使用。
一般情況下,我們可以通過兩種方式解決瀏覽器的同源策略,JSONP和CORS,CORS解決方案更為通用(推薦)。
瀏覽器如何做資源請求的?
瀏覽器和服務(wù)器可以使用超文本傳輸??協(xié)議 (HTTP) 通過網(wǎng)絡(luò)交換數(shù)據(jù)。 HTTP 定義了請求者和響應(yīng)者之間的通信規(guī)則,包括獲取資源所需的信息。
HTTP 頭用于協(xié)商客戶端和服務(wù)器之間的消息交換類型,并用于確定訪問權(quán)限。瀏覽器的請求和服務(wù)器的響應(yīng)消息都分為header和body兩部分:
header
有關(guān)消息的信息,例如消息類型或消息編碼。標頭可以包括表示為鍵值對的各種信息。請求頭和響應(yīng)頭包含不同的信息。
Sample Request header
Accept: text/html
Cookie: Version=1
以上相當于說“我想接收 HTML 作為響應(yīng)。這是我擁有的一個 cookie”。
Sample Response header
Content-Encoding: gzip
Cache-Control: no-store
以上相當于說“數(shù)據(jù)是用gzip編碼的。請不要緩存它?!?/p>
body
消息本身。這可以是純文本、二進制圖像、JSON、HTML 等。
CORS是如何工作的?
同源策略告訴瀏覽器阻止跨源請求。當你想從不同的源獲取公共資源時,資源提供服務(wù)器需要告訴瀏覽器“請求來自的這個源可以訪問我的資源”。瀏覽器記住這一點并允許跨源資源共享。
步驟一:客戶端(瀏覽器)請求
當瀏覽器發(fā)出跨域請求時,瀏覽器會添加一個帶有當前源(方案、主機和端口)的 Origin 標頭。
步驟二:服務(wù)器響應(yīng)
在服務(wù)器端,當服務(wù)器看到此標頭并希望允許訪問時,它需要在響應(yīng)中添加一個 Access-Control-Allow-Origin 標頭,指定請求來源(或 * 以允許任何來源。)
步驟三:瀏覽器收到響應(yīng)
當瀏覽器看到帶有適當 Access-Control-Allow-Origin 標頭的響應(yīng)時,瀏覽器允許與客戶端站點共享響應(yīng)數(shù)據(jù)。
CORS 實戰(zhàn)
打開瀏覽器控制臺。(以Chrome為例,F(xiàn)12即可打開)
輸入以下命令,表現(xiàn)如下:
fetch('https://cors-demo.glitch.me/', {mode:'cors'})
其中https://cors-demo.glitch.me/的響應(yīng)頭如下:
accept-ranges: bytes
cache-control: public, max-age=0
content-length: 53
content-type: application/json; charset=UTF-8
date: Sun, 26 Sep 2021 02:24:57 GMT
etag: W/"35-166a7329ce0"
last-modified: Wed, 24 Oct 2018 17:50:04 GMT
x-powered-by: Express
換另外一個網(wǎng)站試試:
fetch('https://cors-demo.glitch.me/allow-cors', {mode:'cors'})
其中https://cors-demo.glitch.me/allow-cors的響應(yīng)頭如下:
accept-ranges: bytes
access-control-allow-origin: *
cache-control: public, max-age=0
content-length: 53
content-type: application/json; charset=UTF-8
date: Sun, 26 Sep 2021 02:24:37 GMT
etag: W/"35-166a7329ce0"
last-modified: Wed, 24 Oct 2018 17:50:04 GMT
x-powered-by: Express
僅僅因為添加了響應(yīng)頭access-control-allow-origin即解決了跨域問題。、
CORS解決帶cookie跨域問題
出于隱私原因,CORS 通常用于“匿名請求”——請求未識別請求者的請求。如果您想在使用 CORS(可以識別發(fā)送者)時發(fā)送 cookie,您需要向請求和響應(yīng)添加額外的標頭。
請求
添加credentials: 'include'
到請求參數(shù)中即可實現(xiàn)帶cookie進行跨域請求
fetch('https://example.com', {
mode: 'cors',
credentials: 'include'
})
響應(yīng)
Access-Control-Allow-Origin
必須設(shè)置特定的值 (不能使用通配符*
) 并且必須設(shè)置Access-Control-Allow-Credentials 為 true
.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
預(yù)檢請求
如果 Web 應(yīng)用程序需要復(fù)雜的 HTTP 請求,瀏覽器會在請求鏈的前面添加一個預(yù)檢請求。"預(yù)檢請求“的使用,可以避免跨域請求對服務(wù)器的用戶數(shù)據(jù)產(chǎn)生未預(yù)期的影響。
CORS 規(guī)范將復(fù)雜請求定義為
- 請求采用了除 GET, POST, or HEAD之外的請求方法
- 請求采用除
Accept
,Accept-Language
orContent-Language
之前的請求頭 - 請求頭包含
Content-Type
但值不是application/x-www-form-urlencoded
,multipart/form-data
,text/plain
如果需要,瀏覽器會創(chuàng)建預(yù)檢請求。這是一個如下所示的 OPTIONS 請求,并在實際請求消息之前發(fā)送。
OPTIONS /data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: DELETE
在服務(wù)器端,應(yīng)用程序需要使用有關(guān)應(yīng)用程序從該源接受的方法的信息來響應(yīng)預(yù)檢請求。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, DELETE, HEAD, OPTIONS
服務(wù)器響應(yīng)還可以包含一個 Access-Control-Max-Age 標頭,以指定緩存預(yù)檢結(jié)果的持續(xù)時間(以秒為單位),因此客戶端無需在每次發(fā)送復(fù)雜請求時都發(fā)出預(yù)檢請求。
完整請求鏈路如下:
Java中如何解決CORS
為特定請求跨域
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
controller跨域
@CrossOrigin(origins = "http://example.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@RequestMapping(method = RequestMethod.GET, path = "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
混合
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("http://example.com")
@RequestMapping(method = RequestMethod.GET, "/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
全局跨域
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
Spring Security CORS
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()...
}
}
前端如何解決CORS
- 前端代理(Angular自帶)
- Nginx反向代理
ISTIO CORS
https://istio.io/latest/docs/reference/config/networking/virtual-service/#CorsPolicy
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: ratings-route
spec:
hosts:
- ratings.prod.svc.cluster.local
http:
- route:
- destination:
host: ratings.prod.svc.cluster.local
subset: v1
corsPolicy:
allowOrigins:
- exact: https://example.com
allowMethods:
- POST
- GET
allowCredentials: false
allowHeaders:
- X-Foo-Bar
maxAge: "24h"
allowOrigins *
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: ratings-route
spec:
hosts:
- ratings.prod.svc.cluster.local
http:
- route:
- destination:
host: ratings.prod.svc.cluster.local
subset: v1
corsPolicy:
allowOrigins:
- regex: '.*'
allowMethods:
- POST
- GET
allowCredentials: false
allowHeaders:
- X-Foo-Bar
maxAge: "24h"