# 请求流程

CorsFilterCorsInterceptorSpringSecurity

# 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);
    }
}

工作流程(无论是否配置跨域,下图的代码都会执行)

image-20250815165331731

image-20250815165654118

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);
    }

}

工作流程

image-20250815170909644

走上方拦截器那套流程

# 注意!!!

image-20250817122321838

此方式添加的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();
}

image-20250817123256313

SpringSecurity会在鉴权过滤器之前,自动添加CorsFilter过滤器

属于SpringSecurity核心过滤器链的一部分,但是具体逻辑与手动配置的CorsFilter一致

比手动配置CorsFilter的方式高效,因为OPTIONS检测成功之后,会直接返回,不再走后续的过滤器

# 说明

  • 显示配置CorsFilter位于鉴权之后,SpringSecurity配置的CorsFilter位于鉴权之前
  • 2种方式都配置的话,会存在2个CorsFilter,分别位于鉴权之后和之前,没有必要
上次更新: 2025/09/04 17:38:34