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

Spring Cloud微服务间403 Forbidden问题及测试失败修复求助

问题排查与修复方案

一、API Gateway 配置核查

  • 先确认网关已正确转发认证令牌给下游服务,所有请求走网关时丢了Authorization头必然导致403。在网关的Security配置中,要给Auth的开放接口(注册、登录、刷新令牌)开匿名访问,同时确保请求头能传递到Order服务:
@Configuration
public class GatewaySecurityConfig {
    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/auth/**").permitAll() // 放开Auth服务的认证接口
                .anyExchange().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        // 强制转发Authorization头到下游服务
        http.addFilterAfter(new ServerHttpHeadersFilter(), SecurityWebFiltersOrder.AUTHORIZATION);
        return http.build();
    }
}
  • 再检查网关路由配置,确保Order服务的路由未丢失请求头:
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://ORDER-SERVICE
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1
            - PreserveHostHeader # 保留请求头的关键配置

二、Auth服务(认证端)配置调整

  • 认证服务颁发的JWT必须包含用户权限信息,否则Order服务无法判断用户是否有权调用接口。生成令牌时要把用户角色/权限塞进claims:
@Service
public class JwtTokenService {
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        // 将用户权限写入JWT的claims字段
        claims.put("authorities", userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()));
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
                .signWith(SignatureAlgorithm.HS512, "your-secret-key")
                .compact();
    }
}
  • 同时确认Auth服务自身的Security配置放开了注册、登录接口:
@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/auth/register", "/auth/login", "/auth/refresh").permitAll()
                .anyRequest().authenticated();
    }
}

三、Order服务(资源端)配置修复

  • Order服务作为资源服务器,需正确解析JWT令牌并提取权限,这里要注意和Auth服务使用完全相同的密钥,否则令牌验证失败直接返回403:
@Configuration
@EnableResourceServer
public class OrderResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Value("${jwt.secret}")
    private String jwtSecret;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("order-service").tokenStore(tokenStore());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                // 此处权限需和Auth服务颁发的一致,比如仅USER角色可下单
                .antMatchers("/order/placeorder").hasAnyRole("USER")
                .anyRequest().authenticated();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(jwtSecret); // 必须与Auth服务的密钥完全一致
        // 自定义权限提取逻辑,确保从JWT的claims中拿到正确的权限列表
        converter.setAccessTokenConverter(new JwtAccessTokenConverter() {
            @Override
            public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
                Collection<? extends GrantedAuthority> authorities = ((List<?>) claims.get("authorities"))
                        .stream()
                        .map(authority -> new SimpleGrantedAuthority((String) authority))
                        .collect(Collectors.toList());
                return new OAuth2Authentication(null, new UsernamePasswordAuthenticationToken(claims.get("sub"), null, authorities));
            }
        });
        return converter;
    }
}
  • 配置文件中的JWT密钥要和Auth服务完全相同:
jwt:
  secret: your-secret-key # 和Auth服务的密钥完全一致,避免拼写错误

四、OrderControllerTest 测试修复

  • 测试时需要模拟认证状态,有两种常用方式:

方式1:手动生成模拟令牌

@WebMvcTest(OrderController.class)
public class OrderControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private OrderService orderService;

    @Test
    public void testPlaceOrder() throws Exception {
        // 生成带USER权限的模拟JWT
        String mockToken = generateMockToken("test-user", Collections.singletonList("ROLE_USER"));

        mockMvc.perform(post("/order/placeorder")
                .header("Authorization", "Bearer " + mockToken)
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"productId\":1,\"quantity\":2}"))
                .andExpect(status().isOk());
    }

    private String generateMockToken(String username, List<String> roles) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("authorities", roles);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .signWith(SignatureAlgorithm.HS512, "your-secret-key")
                .compact();
    }
}

方式2:用@WithMockUser简化测试

@Test
@WithMockUser(username = "test-user", roles = {"USER"})
public void testPlaceOrder() throws Exception {
    mockMvc.perform(post("/order/placeorder")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"productId\":1,\"quantity\":2}"))
            .andExpect(status().isOk());
}

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 23:45:54