介绍
同源"指的是"三个相同":
- 协议相同
- 域名相同
- 端口相同
同源策略的目的:是为了保证用户信息的安全,防止恶意的网站窃取数据。
限制:如果非同源,共有三种行为受到限制。
- Cookie、LocalStorage、IndexDB无法读取。
- DOM无法获得。
- AJAX请求不能发送。
工作原理是添加新的HTTP headers来让服务器描述哪些源的请求可以访问该资源,对于可能对服务器造成不好影响的请求,规范规定浏览器需要先发送“预检”请求(也就是OPTION请求),在预检请求通过后再发送实际的请求,服务器还可以通知客户端是否应该随请求发送“凭据”(例如 Cookie 和 HTTP 身份验证)。
HTTP Header介绍
Request Header
- Origin:请求的源信息(协议 + 域名 + 端口)
- Access-Control-Request-Method:预检请求类型
- Access-Control-Request-Headers:额外发送的Header信息
Resonse Header
- Access-Control-Allow-Origin:服务器接受的源信息,“*”表示所有
- Access-Control-Allow-Methods:服务器支持的所有跨域请求类型,“*”表示所有
- Access-Control-Allow-Credentials:服务器是否接受Cookies和HTTP Authentication
- Access-Control-Expose-Headers:服务器暴露的额外的Header,“*”表示所有
- Access-Control-Max-Age:本次预检的有效期
- Access-Control-Allow-Credentials设置为true时Access-Control-Allow-Origin不能设置为“*”
@CrossOrigin属性介绍
origins和value
支持的源,origins和value都是相同的配置,互为别名,默认配置是“*”,表示服务器支持所有源的跨域请求,安全信息较低,最好根据实际情况设置对应的信息(协议 + 域名 + 端口)。
originPatterns
同样表示支持的源,Spring 5.3 引入的属性,默认为空,与origins二选一,支持通配符的形式配置origins,比如https://*.domain1.com,该字段为list,也就是可以配置多个。
allowedHeaders
允许跨域的请求头信息,默认为“*”表示允许所有的请求头,CORS默认支持的请求头为:Cache-Control、Content-Language、Expires、Last-Modified、Pragma,如果你需要携带其他的请求头需要设置该属性。
exposedHeaders
服务器允许客户端访问的相应头,默认为空,表示只允许访问:Cache-Control、Content-Language、Expires、Last-Modified、Pragma,如果需要客户端访问其他的相应头需要设置该属性。
methods
服务器允许的Http Request类型,默认是允许GET、POST、HEAD,根据项目需要自行设置。
allowCredentials
浏览器是否需要把凭证(如:cookies、CSRF tokens)发送到服务器,默认是关闭的,因为该选项开启后会与配置的源建立高度信任的关系,并且还会暴露一些敏感信息,所以开启该选项时origin不允许设置为“*”。
maxAge
“预检”结果的缓存时间,单位是秒,默认1800s,在缓存时间内同一请求不需要“预检”请求。
code
@Component
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 不使用*,自动适配跨域域名,避免携带Cookie时失效
String origin = request.getHeader("Origin");
if(StringUtils.isNotBlank(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
}
// 自适应所有自定义头
String headers = request.getHeader("Access-Control-Request-Headers");
if(StringUtils.isNotBlank(headers)) {
response.setHeader("Access-Control-Allow-Headers", headers);
response.setHeader("Access-Control-Expose-Headers", headers);
}
// 允许跨域的请求方法类型
response.setHeader("Access-Control-Allow-Methods", "*");
// 预检命令(OPTIONS)缓存时间,单位:秒
response.setHeader("Access-Control-Max-Age", "3600");
// 明确许可客户端发送Cookie,不允许删除字段即可
response.setHeader("Access-Control-Allow-Credentials", "true");
chain.doFilter(request, response);
}
@Bean
public FilterRegistrationBean registerFilter2() {
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
bean.addUrlPatterns("/*");
bean.setFilter(new CorsFilter());
// 过滤顺序,从小到大依次过滤
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
@Configuration
@Order(1)
public class MyCorsFilter {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 支持请求方式
config.addAllowedMethod("*");
// 允许的原始请求头部信息
config.addAllowedHeader("*");
// 暴露的头部信息
config.addExposedHeader("*");
config.setMaxAge(Duration.ofMinutes(30));
// 2.添加地址映射
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", config);
// 3.返回 CorsFilter 对象
return new CorsFilter(corsConfigurationSource);
}
}