Spring Security 6+Keycloak配置后指定接口返回403问题求助
环境与问题概述
使用 Spring Security 6 + Spring Boot 3.0.4 + Keycloak 20.0.2,目标是通过 Keycloak 中的user角色控制/ap/v1/customer/customerinfo/**接口的访问权限,其他请求全部拒绝。但实际遇到两个问题:
- 按配置运行时,合法角色请求返回403
- 将
.anyRequest().denyAll()改为.anyRequest().authenticated()后,目标接口可访问,但其他接口也能被无限制访问,权限控制失效
现有配置
application.properties
spring.security.oauth2.client.registration.keycloak.client-id=client-id spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.keycloak.scope=openid spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8081/realms/realm_id spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/realm_id spring.security.oauth2.resourceserver.jwt.jwk-set-uri = http://localhost:8081/realms/proset/protocol/openid-connect/certs
Spring Security 配置类
@Configuration @EnableWebSecurity public class SecurityConfiguration { @Bean protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors().configurationSource(new CorsConfigurationSource() { @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration config = new CorsConfiguration(); config.setAllowedHeaders(Collections.singletonList("*")); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowedOriginPatterns(List.of("*")); config.setAllowCredentials(true); return config; } }); http.authorizeHttpRequests() .requestMatchers(HttpMethod.GET, "/ap/v1/customer/customerinfo/**") .hasAnyRole("user") .anyRequest() .denyAll(); http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); return http.build(); }
排查与解决方法
1. 修正角色前缀不匹配问题
Spring Security 的hasAnyRole()方法会自动给角色添加ROLE_前缀,但 Keycloak 默认返回的角色不带该前缀。比如你配置的user角色,Spring 实际会匹配ROLE_user,但 Keycloak 返回的是user,导致匹配失败返回403。
解决方式二选一:
- 在 Keycloak 中创建带
ROLE_前缀的角色(如ROLE_user) - 配置 Spring Security 去掉角色前缀,并指定从正确的JWT字段读取角色:
@Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 禁用默认的ROLE_前缀 grantedAuthoritiesConverter.setAuthorityPrefix(""); // 指定从Keycloak JWT的realm_access.roles字段读取角色 grantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access.roles"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return converter; }
然后在 SecurityFilterChain 中关联该转换器:
http.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) );
2. 修正JWT公钥地址配置错误
注意到jwk-set-uri中的realm是proset,但issuer-uri中的realm是realm_id,两者必须一致,否则Spring无法正确获取公钥验证JWT签名,导致认证失败进而授权失败。
修改jwk-set-uri为:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8081/realms/realm_id/protocol/openid-connect/certs
3. 确认角色存储的JWT字段
Keycloak的JWT中,realm级角色默认放在realm_access.roles数组,client级角色放在resource_access.{client-id}.roles数组。如果你的user是client角色,需要修改转换器的setAuthoritiesClaimName为对应的字段,比如resource_access.client-id.roles(替换为你的实际client-id)。
4. 检查请求匹配规则
确认requestMatchers的路径和请求方法与实际请求完全一致:
- 检查接口路径是否有大小写、斜杠数量错误
- 确认请求方法是否为GET,若使用其他方法(如POST),需调整规则或添加对应方法的匹配
内容的提问来源于stack exchange,提问作者Babak Mirghafari

