CORS原理及解决办法

浏览器的同源策略阻止读取来自不同来源的资源。这种机制阻止恶意站点读取另一个站点的数据,但它也阻止合法使用。

一般情况下,我们可以通过两种方式解决浏览器的同源策略,JSONP和CORS,CORS解决方案更为通用(推荐)。

浏览器如何做资源请求的?

浏览器和服务器可以使用超文本传输​​协议 (HTTP) 通过网络交换数据。 HTTP 定义了请求者和响应者之间的通信规则,包括获取资源所需的信息。

CORS原理及解决办法_第1张图片

HTTP 头用于协商客户端和服务器之间的消息交换类型,并用于确定访问权限。浏览器的请求和服务器的响应消息都分为headerbody两部分:

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 or Content-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 标头,以指定缓存预检结果的持续时间(以秒为单位),因此客户端无需在每次发送复杂请求时都发出预检请求。

完整请求链路如下:

CORS原理及解决办法_第2张图片

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"

你可能感兴趣的:(服务器,运维)