Spring Boot 3集成Keycloak权限配置问题求助
Spring Boot 3 + Spring Security 集成Keycloak问题排查与自定义Token转换器方案
问题描述
在Spring Boot 3与Spring Security新版本中配置Keycloak时遇到两个核心问题:
- 编写
SecurityFilterChain后,系统跳过认证校验逻辑,只要持有有效Bearer Token的用户就能访问受保护的/api/**资源 - Spring Security默认只识别JWT中的
scope作为权限,未将Keycloak颁发的角色转换为Granted Authorities
另外,给JwtAuthConverterProperties添加@Configuration注解后,所有请求被拒绝,询问自定义Token转换器是否可行。
相关代码
WebSecurityConfig.java
@RequiredArgsConstructor @EnableWebSecurity @Configuration public class WebSecurityConfig { private final JwtAuthConverter jwtAuthConverter; @Bean public KeycloakConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Bean public SecurityFilterChain configure(HttpSecurity http) throws Exception { http.oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwtAuthConverter); http.authorizeHttpRequests(auth -> auth .requestMatchers("/api/**","api/**","/api").hasAuthority(ADMINISTRATION) .anyRequest().authenticated() ); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.cors().and().csrf().disable(); return http.build(); } @Bean public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties) { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()).build(); jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(oAuth2ResourceServerProperties.getJwt().getIssuerUri())); return jwtDecoder; } private static final String ADMINISTRATION = "ROLE_administration"; }
JwtAuthConverter.java
@Component public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> { private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); private final JwtAuthConverterProperties properties; public JwtAuthConverter(JwtAuthConverterProperties properties) { this.properties = properties; } @Override public AbstractAuthenticationToken convert(Jwt jwt) { Collection<GrantedAuthority> authorities = Stream.concat( jwtGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream()).collect(Collectors.toSet()); return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt)); } private String getPrincipalClaimName(Jwt jwt) { String claimName = JwtClaimNames.SUB; if (properties.getPrincipalAttribute() != null) { claimName = properties.getPrincipalAttribute(); } return jwt.getClaim(claimName); } private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) { Map<String, Object> resourceAccess = jwt.getClaim("resource_access"); Map<String, Object> resource; Collection<String> resourceRoles; if (resourceAccess == null || (resource = (Map<String, Object>) resourceAccess.get(properties.getResourceId())) == null || (resourceRoles = (Collection<String>) resource.get("roles")) == null) { return Collections.emptySet(); } return resourceRoles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .collect(Collectors.toSet()); } }
JwtAuthConverterProperties.java
@Data @Validated @Configuration @ConfigurationProperties(prefix = "jwt.auth.converter") public class JwtAuthConverterProperties { @NotBlank private String resourceId; private String principalAttribute; }
问题分析与解决方案
1. 自定义Token转换器完全可行
你的JwtAuthConverter实现思路正确,核心逻辑是从JWT的resource_access字段中提取Keycloak角色并转换为Granted Authority,同时保留默认的scope权限。问题出在配置细节上。
2. 跳过认证链的原因与修正
- 路径规则错误:
requestMatchers中的"api/**"缺少前缀斜杠,导致该规则不生效,只有"/api/**"和"/api"被匹配。无效规则会导致权限校验被绕过,只要Token有效就能访问所有/api开头的资源。 - 修正方案:统一路径规则为
"/api/**",覆盖所有子路径。
3. 添加@Configuration后请求被拒绝的原因与修正
JwtAuthConverterProperties同时标注@Configuration和@ConfigurationProperties,会被Spring视为配置类,但该类依赖外部配置的resourceId,如果配置文件中未正确设置jwt.auth.converter.resource-id,@NotBlank校验会失败,导致上下文初始化异常,最终所有请求被拒绝。
- 修正方案:
- 移除
JwtAuthConverterProperties上的@Configuration注解 - 在
WebSecurityConfig中添加@EnableConfigurationProperties(JwtAuthConverterProperties.class)启用配置绑定 - 在配置文件(如
application.yml)中添加Keycloak客户端ID配置:jwt: auth: converter: resource-id: 你的Keycloak客户端ID
- 移除
4. 角色未纳入Granted Authorities的验证点
- 确认
JwtAuthConverter上的@Component注解生效,Spring能扫描到该类 - 检查JWT的
resource_access字段是否包含对应客户端的角色列表,示例格式:
转换器会将其转换为"resource_access": { "你的客户端ID": { "roles": ["administration"] } }ROLE_administration,与你定义的ADMINISTRATION常量匹配。
修正后的核心配置
调整后的WebSecurityConfig.java
@RequiredArgsConstructor @EnableWebSecurity @Configuration @EnableConfigurationProperties(JwtAuthConverterProperties.class) // 启用配置绑定 public class WebSecurityConfig { private final JwtAuthConverter jwtAuthConverter; @Bean public KeycloakConfigResolver keycloakConfigResolver() { return new KeycloakSpringBootConfigResolver(); } @Bean public SecurityFilterChain configure(HttpSecurity http) throws Exception { http.oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwtAuthConverter); http.authorizeHttpRequests(auth -> auth .requestMatchers("/api/**").hasAuthority(ADMINISTRATION) // 统一路径规则 .anyRequest().authenticated() ); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.cors().and().csrf().disable(); return http.build(); } @Bean public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties) { NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()).build(); jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(oAuth2ResourceServerProperties.getJwt().getIssuerUri())); return jwtDecoder; } private static final String ADMINISTRATION = "ROLE_administration"; }
调整后的JwtAuthConverterProperties.java
@Data @Validated @ConfigurationProperties(prefix = "jwt.auth.converter") public class JwtAuthConverterProperties { @NotBlank private String resourceId; private String principalAttribute; }
内容的提问来源于stack exchange,提问作者Ivan Tomić
相关产品推荐
相关产品推荐

