一 简介
同源策略(same origin policy)
是浏览器安全
的基石。在同源策略的限制下,非同源的网站之间不能发送 ajax
请求的。
为了解决这个问题,w3c 提出了跨源资源共享,即CORS([Cross-Origin Resource Sharing]
(https://www.w3.org/TR/cors/))。
CORS 做到了两点:
- 不破坏即有规则
- 服务器实现了 CORS 接口,就可以跨源通信
基于这两点,CORS 将请求分为两类:简单请求和非简单请求。
CORS 出现前的情况
跨源时能够通过script
或者image
标签触发 GET 请求或通过表单发送一条 POST 请求,但这两种请求 HTTP 头
信息中都不能包含任何自定义字段
。
二 简单请求
请求方法是HEAD
、GET
或POS
T 且 HTTP 头信息不超过
以下几个字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type
(只限于 application/x-www-form-urlencoded、multipart/form-data、text/plain)
例如:
GET /test HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.examples.com
Host: www.examples.com
CORS处理策列
后台处理
请求时,在头信息
中添加一个 Origin
字段,服务器收到请求后,根据该字段判断是否允许该请求。
- 如果允许,则在 HTTP 头信息中添加
Access-Control-Allow-Origin
字段,并返回正确的结果
- 如果不允许,则
不在头
信息中添加 Access-Control-Allow-Origin 字段
浏览器处理
浏览器先于
用户得到返回结果,根据有无 Access-Control-Allow-Origin
字段来决定是否拦截
该返回结果。
CORS 的出现,没有
对”旧的“服务造成任何影响。
描述 CORS 返回结果
Access-Control-Allow-Credentials
: 可选,用户是否可以发送、处理 cookie
。
Access-Control-Expose-Headers
:可选,可以让用户拿到的字段
。有几个字段无论设置与否都可以拿到
的,包括:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
三 非简单请求
预检请求
对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次OPTION
请求,称为预检请求
(preflight request)。预检请求将真实请求的信
息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中
,询问服务器是否允许
这样的操作。
比如对于 DELETE 请求:
OPTIONS /test HTTP/1.1
Origin: http://www.examples.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header
Host: www.examples.com
与 CORS 相关的字段有:
Access-Control-Request-Method
: 真实请求使用的 HTTP 方法
。
Access-Control-Request-Headers
: 真实请求中包含的自定义头字段
。
处理策略
后台处理
服务器收到请求时,需要分别对 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 进行验证,验证通过后
,会在返回 Http 头信息中
添加
Access-Control-Allow-Origin: http://www.examples.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
他们的含义分别是:
Access-Control-Allow-Methods
: 真实请求允许的方法
Access-Control-Allow-Headers
: 服务器允许使用的字段
Access-Control-Allow-Credentials
: 是否允许用户发送、处理 cookie
Access-Control-Max-Age
: 预检请求的有效期,单位为秒。有效期内,不会重复
发送预检请求
四 配置 CORS
一个应用可能会有多个 CORS 配置
,并且可以设置每个 CORS 配置针对一个接口
或一系列接
口或者对所有接口
生效。
举例来说,我们需要:
- 让 /test 接口支持跨源访问,而 /test/1 或 /api 等其它接口不支持跨源访问
- 让 /test/* 这一类接口支持跨源访问,而 /api 等其它接口不支持跨源访问
- 站点所有的接口都支持跨源访问
针对某个接口
可以在方法
上添加 CrossOrigin 注解:
@CrossOrigin(origins = {"http://localhost:8083", "null"})
@RequestMapping("/t1")
public String test() {
return "{\"project\":\"just a test\"}";
}
针对一系列接口
可以在类
上添加 CrossOrigin 注解:
@RestController
@CrossOrigin(origins = {"http://localhost:8083", "null"})
@RequestMapping("/test")
public class TestController {
添加全局配置
添加一个配置类
package com.zyc.cros;
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 WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:9000", "null")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
上面已经过时,最新版本的如下
package com.zyc.cros;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebMvcConfg2 extends WebMvcConfigurationSupport {
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:9000", "null")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.maxAge(3600)
.allowCredentials(true);
}
}
添加 Filter 的方式
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://localhost:9000");
config.addAllowedOrigin("null");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
// source.registerCorsConfiguration("/**", config); // CORS 配置对所有接口都有效
source.registerCorsConfiguration("/test/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
建议第一种,第二种设置路径为'/test/**'后,前端不会报403错误。而是空的response。
五 原理
无论是通过哪种方式配置 CORS,其实都是在构造 CorsConfiguration。
一个 CORS 配置用一个 CorsConfiguration 类来表示,它的定义如下:
public class CorsConfiguration {
private List<String> allowedOrigins;
private List<String> allowedMethods;
private List<String> allowedHeaders;
private List<String> exposedHeaders;
private Boolean allowCredentials;
private Long maxAge;
}
Spring MVC 中对 CORS 规则的校验,都是通过委托给DefaultCorsProcessor
实现的。
六 携带cookie
axios默认是发送请求的时候不会带上cookie的,如果需要,则要设置withCredentials: true。
这时后台的Access-Control-Allow-Origin
不能是*
,必须指定明确的Origin值
。
那如果服务器被多个Origin
不同的前端跨域访问呢?
可以动态的对response
设置Access-Control-Allow-Origin
实现过滤器
public class LoginAuthFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(LoginAuthFilter.class);
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rep = (HttpServletResponse) response;
// 设置允许多个域名请求
// String[] allowDomains = {"http://www.toheart.xin","http://192.168.10.213:8080","http://localhost:8080"};
// Set allowOrigins = new HashSet(Arrays.asList(allowDomains));
String originHeads = req.getHeader("Origin");//这样处理,本质就是和*一样。允许所有的Origin访问,但是返回Origin是具体的路径
// if(allowOrigins.contains(originHeads)){
// //设置允许跨域的配置
// // 这里填写你允许进行跨域的主机ip(正式上线时可以动态配置具体允许的域名和IP)
// rep.setHeader("Access-Control-Allow-Origin", originHeads);
// }
rep.setHeader("Access-Control-Allow-Origin", originHeads);
// 设置服务器允许浏览器发送请求都携带cookie
rep.setHeader("Access-Control-Allow-Credentials","true");
// 允许的访问方法
rep.setHeader("Access-Control-Allow-Methods","POST, GET, PUT, OPTIONS, DELETE, PATCH");
// Access-Control-Max-Age 用于 CORS 相关配置的缓存
rep.setHeader("Access-Control-Max-Age", "3600");
rep.setHeader("Access-Control-Allow-Headers","token,Origin, X-Requested-With, Content-Type, Accept,mid,X-Token");
response.setCharacterEncoding("UTF-8");
// response.setContentType("application/json; charset=utf-8");
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
添加fileter
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DateConverter());
}
}
网友评论