浏览器的同源策略阻止读取来自不同来源的资源。这种机制阻止恶意站点读取另一个站点的数据,但它也阻止合法使用。
一般情况下,我们可以通过两种方式解决浏览器的同源策略,JSONP和CORS,CORS解决方案更为通用(推荐)。
浏览器如何做资源请求的?
浏览器和服务器可以使用超文本传输协议 (HTTP) 通过网络交换数据。 HTTP 定义了请求者和响应者之间的通信规则,包括获取资源所需的信息。
HTTP 头用于协商客户端和服务器之间的消息交换类型,并用于确定访问权限。浏览器的请求和服务器的响应消息都分为header和body两部分:
header
有关消息的信息,例如消息类型或消息编码。标头可以包括表示为键值对的各种信息。请求头和响应头包含不同的信息。
Sample Request header
Accept: text/html
Cookie: Version=1
以上相当于说“我想接收 HTML 作为响应。这是我拥有的一个 cookie”。
Sample Response header
Content-Encoding: gzip
Cache-Control: no-store
以上相当于说“数据是用gzip编码的。请不要缓存它。”
body
消息本身。这可以是纯文本、二进制图像、JSON、HTML 等。
CORS是如何工作的?
同源策略告诉浏览器阻止跨源请求。当你想从不同的源获取公共资源时,资源提供服务器需要告诉浏览器“请求来自的这个源可以访问我的资源”。浏览器记住这一点并允许跨源资源共享。
步骤一:客户端(浏览器)请求
当浏览器发出跨域请求时,浏览器会添加一个带有当前源(方案、主机和端口)的 Origin 标头。
步骤二:服务器响应
在服务器端,当服务器看到此标头并希望允许访问时,它需要在响应中添加一个 Access-Control-Allow-Origin 标头,指定请求来源(或 * 以允许任何来源。)
步骤三:浏览器收到响应
当浏览器看到带有适当 Access-Control-Allow-Origin 标头的响应时,浏览器允许与客户端站点共享响应数据。
CORS 实战
打开浏览器控制台。(以Chrome为例,F12即可打开)
输入以下命令,表现如下:
fetch('https://cors-demo.glitch.me/', {mode:'cors'})
其中https://cors-demo.glitch.me/的响应头如下:
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
换另外一个网站试试:
fetch('https://cors-demo.glitch.me/allow-cors', {mode:'cors'})
Copy
其中https://cors-demo.glitch.me/allow-cors的响应头如下:
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
仅仅因为添加了响应头access-control-allow-origin即解决了跨域问题。、
CORS解决带cookie跨域问题
出于隐私原因,CORS 通常用于“匿名请求”——请求未识别请求者的请求。如果您想在使用 CORS(可以识别发送者)时发送 cookie,您需要向请求和响应添加额外的标头。
请求
添加credentials: 'include'
到请求参数中即可实现带cookie进行跨域请求
fetch('https://example.com', {
mode: 'cors',
credentials: 'include'
})
响应
Access-Control-Allow-Origin
必须设置特定的值 (不能使用通配符*
) 并且必须设置Access-Control-Allow-Credentials 为 true
.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
Copy
预检请求
如果 Web 应用程序需要复杂的 HTTP 请求,浏览器会在请求链的前面添加一个预检请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
CORS 规范将复杂请求定义为
- 请求采用了除 GET, POST, or HEAD之外的请求方法
- 请求采用除
Accept
,Accept-Language
orContent-Language
之前的请求头 - 请求头包含
Content-Type
但值不是application/x-www-form-urlencoded
,multipart/form-data
,text/plain
如果需要,浏览器会创建预检请求。这是一个如下所示的 OPTIONS 请求,并在实际请求消息之前发送。
OPTIONS /data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: DELETE
在服务器端,应用程序需要使用有关应用程序从该源接受的方法的信息来响应预检请求。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, DELETE, HEAD, OPTIONS
服务器响应还可以包含一个 Access-Control-Max-Age 标头,以指定缓存预检结果的持续时间(以秒为单位),因此客户端无需在每次发送复杂请求时都发出预检请求。
完整请求链路如下:
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"