什么是跨域资源共享(CORS)?

2023-02-06 10:15:32
## CORS 跨域资源共享(Cross-origin Resource Sharing,CORS)是一个 W3C 标准,允许浏览器向跨域服务器发送请求,CORS 需要浏览器和服务器同时支持,目前主流浏览器(IE10及以上)使用 XMLHttpRequest 对象都可支持该功。 <br/> CORS 整个通信过程都是浏览器自动完成,浏览器一旦发现 ajax 请求跨源,就会自动在头信息中增加 Origin 字段,用来说明本次请求来自哪个源(`协议 `+ `域名` + `端口`)。因此,实现 CORS 通信的关键是服务器,需要服务器配置 `Access-Control-Allow-Origin` 头信息。当 CORS 请求需要携带 cookie 时,需要服务端配置 Access-Control-Allow-Credentials 头信息,前端也需要设置 withCredentials。 <br/> 浏览器将 CORS 请求分成两类:`简单请求` 和 `非简单请求`。简单请求需要满足以下两大条件: * 请求方法是以下三种方法之一:HEAD、GET、POST。 * HTTP 的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain。 CORS 简单请求跨域实现流程 ![cors.png](https://static.daimaku.net/post/202302/06/50ee7f168601baf47c844267a07b4273.png) 如果是 `非简单请求`,浏览器会先使用 `OPTIONS` 方法发起一个预检请求,预检请求获知服务器是否允许该跨域请求,如果允许才发起第二次真实的请求,如果不允许,则拦截第二次请求。 ## 前端 CORS 简单请求跨域代码示例 ``` var xhr = new XMLHttpRequest(); // IE8、9 需用 XDomainRequest 兼容 xhr.withCredentials = true; // 前端设置是否带cookie xhr.open('post', 'http://localhost:8080/api/login', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=jack&password=123456'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } }; ``` 如果前端使用 **axios** 发起 ajax 请求,不需要特别处理,示例如下: ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Demo</title> <script src="https://cdn.bootcss.com/qs/6.7.0/qs.min.js"></script> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> </head> <body> <button onclick="test()">测试</button> <script> var qs = Qs function test() { var data = { username: 'jack', password: '123456' } axios({ method: 'POST', url:'http://localhost:8080/api/login', headers: {'content-type': 'application/x-www-form-urlencoded'}, data: qs.stringify(data), }).then(res => { console.log(res); }) } </script> </body> </html> ``` 下面是一个 `非简单` 请求的示例,会先发起 OPTIONS 类型的请求(因为 Content-Type 不符合简单请求的规定) ``` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Demo</title> </head> <body> <button onclick="test2()">测试2</button> <script src="https://cdn.bootcss.com/qs/6.7.0/qs.min.js"></script> <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> <script> function test2() { var data = { username: 'jack', password: '123456' } axios({ method: 'POST', url:'http://localhost:8080/api/test', headers: { 'content-type': 'application/json' }, data: data, }).then(res => { console.log(res); }) } </script> </body> </html> ``` ## 后端 Java Spring Boot 项目,在控制器类或方法上添加 `@CrossOrigin` 注解后,我们可以观察到响应的头信息中,增加了 `Access-Control-Allow-Origin` 相关信息 ``` import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController @CrossOrigin public class TestController { @PostMapping("/api/login") public String version(String username, String password) { System.out.println(username); System.out.println(password); return "hello"; } } ``` 发起请求 ``` $ curl -i 'http://localhost:8080/api/login' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'Origin: localhost' \ --data-raw 'username=jack&password=123456' ``` 观察响应数据 ``` HTTP/1.1 200 Access-Control-Allow-Origin: * Content-Type: text/plain;charset=UTF-8 Content-Length: 5 hello ``` 也可以用下面的方式配置全局 CORS ``` import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.PostConstruct; @Configuration public class Cors implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // .allowedOrigins("*") // 设置允许跨域请求的域名 .allowedOriginPatterns("*") // SpringBoot升级2.4.0+ .allowedMethods("*") // 设置允许的方法 "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS" .allowCredentials(true) // 是否允许 cookies .maxAge(3600) .allowedHeaders("*"); } } ``` ## 扩展阅读 [什么是浏览器的同源策略?](https://www.daimaku.net/post/view/23091)