You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot微服务+Vue经API网关请求报CORS跨域错误

Spring Cloud Gateway 新增鉴权过滤器后跨域问题

问题背景

我正在开发后端采用Spring Boot微服务架构、前端采用Vue.js的项目,项目结构如下:
项目结构
此前在控制器类上添加@CrossOrigin注解即可规避CORS错误,项目一直运行正常,直到新增支持用户登录的安全模块后出现跨域问题。

已完成配置

    1. 在基于spring-cloud-gateway搭建的API网关中新增AuthFilter拦截器,用于JWT的创建与校验,文件路径:api-gateway/src/main/java/.../AuthFilter.java
@Component
public class AuthFilter extends AbstractGatewayFilterFactory<AuthFilter.Config> {
    private final WebClient.Builder webClientBuilder;

    @Autowired
    public AuthFilter(WebClient.Builder webClientBuilder) {
        super(Config.class);
        this.webClientBuilder = webClientBuilder;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if(!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                throw new RuntimeException("Missing auth information");
            }

            String authHeader = exchange.getRequest().getHeaders().get(org.springframework.http.HttpHeaders.AUTHORIZATION).get(0);
            String[] parts = authHeader.split(" ");

            if(parts.length != 2 || !"Bearer".equals(parts[0])) {
                throw new RuntimeException("Incorrect auth structure");
            }

            return webClientBuilder.build()
                    .post()
                    .uri("http://manager-service/api/v1/auth/validateToken?token=" + parts[1])
                    .retrieve()
                    .bodyToMono(EmployeeDTO.class) //EmployeeDTO.class is custom DTO that represents User
                    .map(user -> {
                        exchange.getRequest()
                                .mutate()
                                .header("x-auth-user-id", user.getId());
                        return exchange;
                    }).flatMap(chain::filter);

        };
    }

    public static class Config {
        //留空,无需额外配置
    }
}
    1. 在网关的application.properties中为各服务路由绑定AuthFilter过滤器,文件路径:api-gateway/src/resource/application.properties
##Workshop service routes
spring.cloud.gateway.routes[0].id=workshop-service
spring.cloud.gateway.routes[0].uri=lb://workshop-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/v1/workshop/**
spring.cloud.gateway.routes[0].filters[0]=AuthFilter

##Manage service routes
spring.cloud.gateway.routes[1].id=manager-service
spring.cloud.gateway.routes[1].uri=lb://manager-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/v1/manage/**
spring.cloud.gateway.routes[1].filters[0]=AuthFilter

##登录接口路由,无需挂载AuthFilter,对所有用户开放
spring.cloud.gateway.routes[2].id=manager-service-sign-in
spring.cloud.gateway.routes[2].uri=lb://manager-service
spring.cloud.gateway.routes[2].predicates[0]=Path=/api/v1/auth/signIn

...
    1. manager-service微服务负责用户、角色、组织等基础实体管控,承担JWT生成职责,在该服务中新增了SecurityConfig和WebConfig配置
    • SecurityConfig.java代码:
    @EnableWebSecurity
    public class SecurityConfig  {
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .csrf().disable()
                    .authorizeRequests().anyRequest().permitAll();
            return httpSecurity.build();
        }
       }
    
    • WebConfig.java代码:
    @EnableWebMvc
    public class WebConfig implements WebMvcConfigurer {
    
        private static final Long MAX_AGE=3600L;
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedHeaders(
                            HttpHeaders.AUTHORIZATION,
                            HttpHeaders.CONTENT_TYPE,
                            HttpHeaders.ACCEPT)
                    .allowedMethods(
                            HttpMethod.GET.name(),
                            HttpMethod.POST.name(),
                            HttpMethod.PUT.name(),
                            HttpMethod.DELETE.name())
                    .maxAge(MAX_AGE)
                    .allowedOrigins("http://localhost:8100")
                    .allowCredentials(false);
        }
    }
    
    1. 在鉴权控制器类上添加了@CrossOrigin注解,文件路径:manager-service/src/main/java/.../AuthController.java
@RestController
@RequestMapping("api/v1/auth")
@CrossOrigin(origins = "http://localhost:8100")
@Slf4j
public class AuthController {
    private final AuthService authService;

    @Autowired
    public AuthController(AuthService authService) {
        this.authService = authService;
    }

    @PostMapping("/signIn")
    public ResponseEntity<EmployeeDTO> signIn(@RequestBody CredentialsDTO credentialsDTO) {
        log.info("Trying to login {}", credentialsDTO.getLogin());

        return ResponseEntity.ok(EmployeeMapper.convertToDTO(authService.signIn(credentialsDTO)));
    }

    @PostMapping("/validateToken")
    public ResponseEntity<EmployeeDTO> validateToken(@RequestParam String token) {
        log.info("Trying to validate token {}", token);
        Employee validatedTokenUser = authService.validateToken(token);
        return ResponseEntity.ok(EmployeeMapper.convertToDTO(validatedTokenUser));
    }
}
    1. 前端Vue.js项目通过axios发起请求,登录POST请求代码如下:
axios.post('http://localhost:8080/api/v1/auth/signIn', this.credentials).then(response => {
              console.log('response = ', response)
              console.log('token from response', response.data.token)
              this.$store.commit('saveToken', response.data.token)
            }).catch(error => {
          console.log('Error is below')
          console.log(error)
        })

问题现象

所有请求均触发如下错误:
Access to XMLHttpRequest at 'http://localhost:8080/api/v1/auth/signIn' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Chrome中展示的请求头如下:
请求头截图

此前尝试新增额外corsConfiguration配置、将类上的@CrossOrigin注解移到方法上,均未生效。使用Postman发起请求可正常返回令牌。

排查进展

经定位问题出在api-gateway层:直接请求后端服务可得到正确响应,经过网关转发时就会报错,网关日志如下:

2022-07-05 00:34:18.128 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/api/v1/workshop/**]" does not match against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/api/v1/manage/**]" does not match against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/api/v1/auth/signIn" matches against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: manager-service-sign-in
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: OPTIONS http://localhost:8080/api/v1/auth/signIn] to Route{id='manager-service-sign-in', uri=lb://manager-service, order=0, predicate=Paths: [/api/v1/auth/signIn], match trailing slash: true, gatewayFilters=[], metadata={}}
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : [e5b87280-8] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@78df1cfc

问题根因

  1. 跨域配置位置错误:Spring Cloud Gateway是基于WebFlux的响应式网关,作为所有请求的统一入口,浏览器发起的OPTIONS预检请求会先到达网关,下游微服务配置的@CrossOrigin、WebConfig跨域规则根本不会生效。网关本身没有配置CORS规则时,预检请求拿不到合法的跨域响应头就会被拦截。
  2. 鉴权过滤器逻辑缺陷:浏览器发送OPTIONS预检请求时不会携带Authorization请求头,现有AuthFilter逻辑会直接抛出缺失鉴权信息的异常,导致请求直接失败,无法返回正确的跨域头。

解决方案

1. 网关层统一配置全局CORS

删除下游所有微服务里的跨域配置(包括WebConfig类、控制器上的@CrossOrigin注解),避免跨域头重复返回导致新问题,在API网关层统一处理跨域,以下两种配置方式二选一即可:

  • 配置文件方式(在网关的application.properties中添加):
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedOrigins=http://localhost:8100
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedHeaders=*
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowedMethods=GET,POST,PUT,DELETE,OPTIONS
spring.cloud.gateway.globalcors.corsConfigurations.[/**].allowCredentials=false
spring.cloud.gateway.globalcors.corsConfigurations.[/**].maxAge=3600
  • Java配置类方式(在网关服务中新增配置类):
@Configuration
public class GatewayCorsConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("http://localhost:8100"));
        config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
        config.addAllowedHeader("*");
        config.setAllowCredentials(false);
        config.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

注意:网关是WebFlux运行环境,跨域配置要使用CorsWebFilter,不要用Spring MVC的WebMvcConfigurer,否则配置不会生效。

2. 修改鉴权过滤器逻辑,放行OPTIONS请求

在AuthFilter的逻辑最前面添加OPTIONS请求放行判断,避免预检请求被鉴权逻辑拦截:

@Override
public GatewayFilter apply(Config config) {
    return (exchange, chain) -> {
        // 直接放行OPTIONS预检请求
        if (exchange.getRequest().getMethod() == HttpMethod.OPTIONS) {
            return chain.filter(exchange);
        }
        // 原有鉴权逻辑保持不变
        if(!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
            throw new RuntimeException("Missing auth information");
        }

        String authHeader = exchange.getRequest().getHeaders().get(org.springframework.http.HttpHeaders.AUTHORIZATION).get(0);
        String[] parts = authHeader.split(" ");

        if(parts.length != 2 || !"Bearer".equals(parts[0])) {
            throw new RuntimeException("Incorrect auth structure");
        }

        return webClientBuilder.build()
                .post()
                .uri("http://manager-service/api/v1/auth/validateToken?token=" + parts[1])
                .retrieve()
                .bodyToMono(EmployeeDTO.class)
                .map(user -> {
                    exchange.getRequest()
                            .mutate()
                            .header("x-auth-user-id", user.getId());
                    return exchange;
                }).flatMap(chain::filter);
    };
}

配置完成后重启网关服务即可解决跨域问题。


内容的提问来源于stack exchange,提问作者Allteran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 08:33:11