WebFlux整合Keycloak 自定义JWT roles实现@PreAuthorize安全未生效
问题根因定位与修复方案
核心问题排查
- 自定义权限转换器选错了技术栈实现类:你当前编写的
GrantedAuthoritiesExtractor继承的是Servlet(WebMVC)栈下的JwtAuthenticationConverter,虽然外层套了ReactiveJwtAuthenticationConverterAdapter做适配,但因为链路配置的优先级问题,这套自定义逻辑根本没有被实际加载到请求处理链路中。 - 自定义过滤链优先级低于Spring Boot自动配置的默认链:你当前定义的
SecurityWebFilterChain没有显式设置优先级,Spring Boot的OAuth2自动配置会生成一套默认的过滤链,优先级高于你未声明顺序的自定义链。所有请求都会先被默认链拦截,使用默认的JWT权限转换器——这个默认转换器的内置逻辑就是提取JWT中的scope声明,拼接SCOPE_前缀作为权限项,这就是为啥你写@PreAuthorize("hasAuthority('SCOPE_email')")可以生效,而自定义的角色权限完全不生效。 - 权限提取逻辑没有完全隔离默认行为:就算自定义链生效,你使用的Servlet版转换器默认会开启
scope提取、SCOPE_前缀拼接的逻辑,没有清空默认前缀的情况下,你提取的业务角色也会被加上不必要的前缀,和方法级权限注解的匹配规则对不上。
具体修复步骤
1. 重写自定义权限转换器,直接使用WebFlux原生实现
替换原有基于Servlet栈的转换器,改用WebFlux专属的ReactiveJwtAuthenticationConverter实现,完全覆盖默认的scope提取逻辑、清空默认权限前缀:
@Bean public Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() { ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter() { @Override protected Mono<Collection<GrantedAuthority>> extractAuthorities(Jwt jwt) { // 直接提取JWT顶层自定义roles数组,完全替代默认的scope提取逻辑 Collection<?> customRoles = (Collection<?>) jwt.getClaims().getOrDefault("roles", Collections.emptyList()); List<GrantedAuthority> authorities = customRoles.stream() .map(Object::toString) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); return Mono.just(authorities); } }; // 清空默认的SCOPE_前缀,避免业务角色被强制加前缀导致匹配失败 converter.setAuthorityPrefix(""); return converter; }
2. 提升自定义过滤链优先级,覆盖自动配置的默认链
给自定义的SecurityWebFilterChain添加最高优先级注解,确保所有请求优先走自定义配置的链路,而不是自动生成的默认链:
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityWebFilterChain configure(ServerHttpSecurity http) { http .authorizeExchange() .anyExchange().authenticated() .and() .oauth2Login() .and() .oauth2ResourceServer() .jwt() .jwkSetUri(jwkSetUri) .jwtAuthenticationConverter(grantedAuthoritiesExtractor()); return http.build(); }
3. 消除配置冲突
- 删除冗余的
ReactiveJwtDecoderBean配置,统一使用jwkSetUri的配置方式,避免Java代码与application.yml中的资源服务器配置重复冲突。 - 检查application.yml中
resourceserver.jwt的配置,移除和Java代码重复的issuer-uri、jwk-set-uri配置,避免自动配置覆盖自定义的权限转换器逻辑。
验证方式
在自定义的extractAuthorities方法中添加日志,打印提取到的角色列表、最终生成的权限集合,确认请求被拦截时,认证对象中包含JWT顶层roles数组里的ROLE_ADMIN权限项,此时即可正常使用@PreAuthorize("hasAuthority('ROLE_ADMIN')")做方法级权限控制。
内容的提问来源于stack exchange,提问作者spring00
相关产品推荐
相关产品推荐

