# 请求流程
CorsFilter → CorsInterceptor → SpringSecurity
# WebMvcConfigurer(拦截器,不推荐)
核心配置
@Configuration
@AllArgsConstructor
public class WebMvcAutoConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:3208",
"http://127.0.0.1:3208"
)
.allowedMethods("GET", "POST", "OPTIONS")
.allowCredentials(true)
.maxAge(1800);
}
}
工作流程(无论是否配置跨域,下图的代码都会执行)


public static boolean isCorsRequest(HttpServletRequest request) {
String origin = request.getHeader(HttpHeaders.ORIGIN);
if (origin == null) {
return false;
}
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
String scheme = request.getScheme();
String host = request.getServerName();
int port = request.getServerPort();
return !(ObjectUtils.nullSafeEquals(scheme, originUrl.getScheme()) &&
ObjectUtils.nullSafeEquals(host, originUrl.getHost()) &&
getPort(scheme, port) == getPort(originUrl.getScheme(), originUrl.getPort()));
}
CorsUtils.isCorsRequest(request),校验是否是跨域请求,是,往下处理;否,直接返回
public static boolean isPreFlightRequest(HttpServletRequest request) {
return (HttpMethod.OPTIONS.matches(request.getMethod()) &&
request.getHeader(HttpHeaders.ORIGIN) != null &&
request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
}
CorsUtils.isPreFlightRequest(request),校验是否是预检OPTIONS请求
if (config == null) {
if (preFlightRequest) {
rejectRequest(new ServletServerHttpResponse(response));
return false;
}
else {
return true;
}
}
return handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
然后,判断CorsConfiguration(如果没配置CorsFilter或者,该值为null)是否为null
如果为null,那么判断是否OPTIONS请求
是,直接返回跨域提示(config为null,说明未配置跨域,那么不允许跨域)
否,则直接返回,OPTIONS请求通过
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
if (allowOrigin == null) {
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
if (allowMethods == null) {
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
rejectRequest(response);
return false;
}
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
List<String> allowHeaders = checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
rejectRequest(response);
return false;
}
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
if (preFlightRequest && !CollectionUtils.isEmpty(allowHeaders)) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) &&
Boolean.parseBoolean(request.getHeaders().getFirst(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK))) {
responseHeaders.set(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.toString(true));
}
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}
跨域检测通过,会直接返回
response.flush();
return true;
# CorsFilter
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedOriginPattern("*");
corsConfiguration.addAllowedMethod("*");
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}
工作流程

走上方拦截器那套流程
# 注意!!!

此方式添加的CorsFilter在过滤器链中位于SpringSecurity的核心过滤器链springSecurityFilterChain之后,执行顺序晚于springSecurityFilterChain
所以,在执行跨域检测之前,会先执行SpringSecurity的流程
如果接口未忽略鉴权,那么,OPTIONS请求会失败(该类型不携带TOKEN信息),表现在前端就是CORS错误,但核心原因是鉴权失败
如果响应代码为
403,响应信息为Invalid CORS request,那么就是跨域的问题
但是,如果将接口忽略鉴权,那么接口又会拿不到权限信息
所以,最好让SpringSecurity放行OPTIONS请求,这样,就不必忽略接口鉴权,接口还是会拿到鉴权信息
/**
* 核心过滤器链
*/
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
security
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
//预检请求,放行
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
xxxxxxxxxx其他配置
//其他接口,校验
.anyRequest()
.authenticated()
)
;
return security.build();
}
核心代码
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
# 缺点
效率较低,在执行CorsFilter之前,要先执行springSecurityFilterChain全部的过滤器
# Spring Security(推荐)
/**
* 跨域配置
* bean名字必须叫corsConfigurationSource
* <p>
* cors(Customizer.withDefaults())会自动查找名为corsConfigurationSource的bean
*/
@Bean
public CorsConfigurationSource corsConfigurationSource(CorsProperties corsProperties) {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
//允许cookie跨域
corsConfiguration.setAllowCredentials(true);
//允许访问头部信息
corsConfiguration.addAllowedHeader("*");
/*
* 允许访问的域,支持通配符,
* 192.168.1.* 表示允许所有来自192.168.1开头IP的请求
* *.xianyu.info 表示允许所有来自xianyu.info子域的请求
* */
corsConfiguration.setAllowedOriginPatterns(corsProperties.getAllowedOrigin());
//允许的方法
corsConfiguration.setAllowedMethods(corsProperties.getAllowedMethod());
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
/**
* 核心过滤器链
*/
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
security
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
//预检请求,放行
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
xxxxxxxxxx其他配置
//其他接口,校验
.anyRequest()
.authenticated()
)
;
return security.build();
}

SpringSecurity会在鉴权过滤器之前,自动添加CorsFilter过滤器
属于SpringSecurity核心过滤器链的一部分,但是具体逻辑与手动配置的CorsFilter一致
比手动配置CorsFilter的方式高效,因为OPTIONS检测成功之后,会直接返回,不再走后续的过滤器
# 说明
- 显示配置
CorsFilter位于鉴权之后,SpringSecurity配置的CorsFilter位于鉴权之前 - 2种方式都配置的话,会存在2个
CorsFilter,分别位于鉴权之后和之前,没有必要
← 简介