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

Spring Boot微服务GET接口返回403 Forbidden问题排查

Spring Cloud Gateway下GET接口返回403 Forbidden问题排查

我基于SpringBoot + Eureka Server搭建微服务架构,包含users微服务、Eureka Server和Spring Cloud Gateway网关模块。

Users微服务Controller代码

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private Environment env;

    @Autowired
    UsersService usersService;

    @GetMapping("/status/check")
    public String status(){
        System.out.println("hereeeeeeeeeee");
        return "Working "+env.getProperty("local.server.port");
    }

    @PostMapping
    public ResponseEntity<CreateUserResponseModel> createUser(@Valid @RequestBody CreateUserRequestModel userDetails) {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
        UserDto userDto = modelMapper.map(userDetails, UserDto.class);
        UserDto createdUser = usersService.createUser(userDto);
        CreateUserResponseModel body = modelMapper.map(createdUser, CreateUserResponseModel.class);
        return  ResponseEntity.status(HttpStatus.CREATED).body(body);
    }
    
}

问题现象

POST /users接口正常运行,但GET /users/status/check接口返回403 Forbidden错误:
错误截图

网关AuthorizationHeaderFilter代码

网关中自定义了JWT校验过滤器,已确认该过滤器能正常解码JWT:

@Component
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config>{

    @Autowired
    private Environment environment;

    public AuthorizationHeaderFilter(){
        super(Config.class);
    }

    public static class Config{

    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange,chain)->{
            ServerHttpRequest request = exchange.getRequest();
            if(!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)){
                return onError(exchange,"No authorization header",HttpStatus.UNAUTHORIZED);
            }
            String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
            String jwt = authorizationHeader.replace("Bearer ", ""); //Make sure you add space
            if(!isJwtValid(jwt)){
                return onError(exchange, "Jwt token is invalid",HttpStatus.UNAUTHORIZED);
            }
            return chain.filter(exchange);
        };
    }

    private Mono<Void> onError(ServerWebExchange exchange,String err,HttpStatus httpStatus){
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        return response.setComplete();
    }

    private boolean isJwtValid(String jwt){
        System.out.println("1");
        boolean isValid = false;
        String subject = null;
        String tokenSecret = environment.getProperty("token.secret");
        System.out.println("2 "+tokenSecret);
        SecretKey secretKey = Keys.hmacShaKeyFor(tokenSecret.getBytes());

        JwtParser jwtParser = Jwts.parser().verifyWith(secretKey).build();
        try{
            // Jwt<Header,Claims> parsedToken = jwtParser.parse(jwt);
            subject = jwtParser.parseSignedClaims(jwt).getPayload().get("userId").toString();
            System.out.println("3 "+subject);
        } catch(Exception ex){
            isValid = false;
            System.out.println("4 "+ex.getLocalizedMessage());
        }

        if(subject == null || subject.isEmpty()){
            isValid = false;
        } else {
            isValid = true;
        }
        System.out.println("5 ");
        return isValid;
    }

  
}

网关application.properties配置

spring.application.name=api-gateway
server.port=8082
eureka.client.service-url.defaultZone=http://localhost:8010/eureka

spring.cloud.gateway.routes[0].id=users-status-check
spring.cloud.gateway.routes[0].uri=lb://users-ws
spring.cloud.gateway.routes[0].predicates[0]=Path=/users-ws/users/status/check
spring.cloud.gateway.routes[0].predicates[1]=Method=GET
spring.cloud.gateway.routes[0].predicates[2]=Header=Authorization, Bearer (.*)
spring.cloud.gateway.routes[0].filters[0]=RemoveRequestHeader=Cookie
spring.cloud.gateway.routes[0].filters[1]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}
spring.cloud.gateway.routes[0].filters[2]=AuthorizationHeaderFilter

spring.cloud.gateway.routes[1].id=users-ws
spring.cloud.gateway.routes[1].uri=lb://users-ws
spring.cloud.gateway.routes[1].predicates[0]=Path=/users-ws/users
spring.cloud.gateway.routes[1].predicates[1]=Method=POST
spring.cloud.gateway.routes[1].filters[0]=RemoveRequestHeader=Cookie
spring.cloud.gateway.routes[1].filters[1]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}

spring.cloud.gateway.routes[2].id=users-ws-h2-console
spring.cloud.gateway.routes[2].uri=lb://users-ws
spring.cloud.gateway.routes[2].predicates[0]=Path=/users-ws/h2-console
spring.cloud.gateway.routes[2].predicates[1]=Method=GET
spring.cloud.gateway.routes[2].filters[0]=RemoveRequestHeader=Cookie
spring.cloud.gateway.routes[2].filters[1]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}

spring.cloud.gateway.routes[3].id=users-ws-login
spring.cloud.gateway.routes[3].uri=lb://users-ws
spring.cloud.gateway.routes[3].predicates[0]=Path=/users-ws/users/login
spring.cloud.gateway.routes[3].predicates[1]=Method=POST
spring.cloud.gateway.routes[3].filters[0]=RemoveRequestHeader=Cookie
spring.cloud.gateway.routes[3].filters[1]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}

spring.cloud.gateway.routes[4].id=users-ws-get-update-delete
spring.cloud.gateway.routes[4].uri=lb://users-ws
spring.cloud.gateway.routes[4].predicates[0]=Path=/users-ws/users/**
spring.cloud.gateway.routes[4].predicates[1]=Method=GET,PUT,DELETE
spring.cloud.gateway.routes[4].predicates[2]=Header=Authorization, Bearer (.*)
spring.cloud.gateway.routes[4].filters[0]=RemoveRequestHeader=Cookie
spring.cloud.gateway.routes[4].filters[1]=AuthorizationHeaderFilter
spring.cloud.gateway.routes[4].filters[2]=RewritePath=/users-ws/(?<segment>.*), /$\{segment}

token.secret=1aBcD3eF4gH5iJ6kL7mN8oP9qRsTuVwXyZ0A1bCdEfG2hI3jK4lM5nO6pQ7rStU8vW9xYz

问题排查与解决方案

1. 确认请求是否到达Users微服务

查看Users微服务控制台,检查是否打印了hereeeeeeeeeee:

  • 如果没打印:说明请求未通过网关转发到Users服务,需检查网关路由匹配逻辑,比如第一个路由的Header断言是否严格匹配Authorization: Bearer xxx格式,或者路由顺序是否被后面的模糊路由(第四个)提前匹配。
  • 如果打印了:说明请求已到达Users服务,但被服务端安全拦截,核心问题在Users微服务的Spring Security配置。

2. 检查Users微服务的Spring Security配置

如果Users微服务引入了spring-boot-starter-security依赖,默认会启用认证拦截,需确保/users/status/check端点允许匿名访问:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                // 允许GET /users/status/check匿名访问
                .requestMatchers(HttpMethod.GET, "/users/status/check").permitAll()
                // 其他接口需认证
                .anyRequest().authenticated()
            )
            // 临时关闭CSRF测试(如果是CSRF导致)
            .csrf(csrf -> csrf.disable());
        return http.build();
    }
}

3. 排查CSRF防护影响

Spring Security默认CSRF防护仅拦截POST/PUT/DELETE等修改请求,但如果自定义配置开启了对GET请求的CSRF校验,会导致GET请求因缺少CSRF Token返回403。可以临时关闭CSRF(如上代码)验证是否解决问题,若解决则需调整CSRF配置,忽略该GET端点。

4. 验证网关过滤器逻辑

虽然已确认JWT能正常解码,但需检查过滤器是否在JWT校验通过后正确转发请求,或是否在流程中意外修改了请求头导致服务端拒绝。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:28:09