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
相关产品推荐
相关产品推荐

