禁用Keycloak登录重定向后Spring Boot出现authenticationManager为空错误
问题背景
我在API网关的Security配置中禁用了Keycloak登录重定向(仅想在用户微服务中使用Keycloak),添加代码http.exceptionHandling().authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/form-login"))后,出现如下错误:
Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalArgumentException: authenticationManager cannot be null
相关代码
SecurityConfig类
@EnableGlobalMethodSecurity(prePostEnabled = true) @Configuration @EnableWebFluxSecurity public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception { http .securityMatcher(new NegatedServerWebExchangeMatcher( ServerWebExchangeMatchers.pathMatchers("/css/**","/contact-us","/actuator/**","/isalive/**", "/api/v1/auth/**","/login", "/signup", "/publicOffers"))) .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oauth2ResourceServer() .jwt().jwtAuthenticationConverter(grantedAuthoritiesExtractor()); http.exceptionHandling().authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/form-login")) .and() .httpBasic(); http.csrf().disable(); return http.build(); } private Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() { GrantedAuthoritiesExtractor extractor = new GrantedAuthoritiesExtractor(); return new ReactiveJwtAuthenticationConverterAdapter(extractor); } @Override protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); } static class GrantedAuthoritiesExtractor extends JwtAuthenticationConverter { @Override protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) { Map<String, Object> claims = jwt.getClaims(); JSONObject realmAccess = (JSONObject) claims.get("realm_access"); if(realmAccess!=null){ JSONArray roles = (JSONArray) realmAccess.get("roles"); return roles.stream() .map(Object::toString) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } return null; } } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); auth.authenticationProvider(keycloakAuthenticationProvider); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
application.properties配置
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8180/realms/myrealm spring.security.oauth2.client.registration.spring-cloud-gateway-client.client-id=SpringTest-client spring.security.oauth2.client.registration.spring-cloud-gateway-client.client-secret=E9Pk0mgRYThmvaq9wt38Spi7jpzkoc8l spring.security.oauth2.client.registration.spring-cloud-gateway-client.provider=keycloak spring.security.oauth2.client.registration.spring-cloud-gateway-client.authorization-grant-type=authorization_code spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/realms/myrealm
需求
希望修复该错误,并了解如何修改由spring.security.oauth2.client提供的authenticationManager Bean。
解决方案
1. 错误原因分析
添加httpBasic()配置后,WebFlux环境下的HttpBasicSpec需要关联ReactiveAuthenticationManager实例,但当前配置未正确提供该Bean,导致authenticationManager为空。另外,你混合使用了Servlet环境的KeycloakWebSecurityConfigurerAdapter和WebFlux的SecurityWebFilterChain,两者适配环境冲突,这是AuthenticationManager无法正确注入的核心原因。
2. 修复步骤
步骤1:移除Servlet环境的Keycloak适配代码
删除extends KeycloakWebSecurityConfigurerAdapter,同时移除以下Servlet相关配置方法:
configureGlobal(AuthenticationManagerBuilder auth)authenticationManagerBean()sessionAuthenticationStrategy()
步骤2:添加WebFlux环境的ReactiveAuthenticationManager
创建Reactive版本的认证管理器Bean,用于HttpBasic认证(示例为模拟逻辑,需替换为真实用户校验逻辑):
@Bean public ReactiveAuthenticationManager reactiveAuthenticationManager() { return authentication -> { // 此处替换为对接用户微服务的认证逻辑 if ("user".equals(authentication.getPrincipal()) && "password".equals(authentication.getCredentials())) { return Mono.just(new UsernamePasswordAuthenticationToken( authentication.getPrincipal(), authentication.getCredentials(), Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) )); } return Mono.error(new BadCredentialsException("Invalid credentials")); }; }
步骤3:调整SecurityWebFilterChain配置
将httpBasic()关联到上述ReactiveAuthenticationManager,并简化配置结构:
@Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveAuthenticationManager reactiveAuthenticationManager) throws Exception { http .securityMatcher(new NegatedServerWebExchangeMatcher( ServerWebExchangeMatchers.pathMatchers("/css/**","/contact-us","/actuator/**","/isalive/**", "/api/v1/auth/**","/login", "/signup", "/publicOffers"))) .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()))) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/form-login"))) .httpBasic(basic -> basic.authenticationManager(reactiveAuthenticationManager)) .csrf().disable(); return http.build(); }
步骤4:优化权限提取器(避免空指针)
原GrantedAuthoritiesExtractor返回null会触发异常,改为返回空集合:
static class GrantedAuthoritiesExtractor extends JwtAuthenticationConverter { @Override protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) { Map<String, Object> claims = jwt.getClaims(); JSONObject realmAccess = (JSONObject) claims.get("realm_access"); if(realmAccess != null){ JSONArray roles = (JSONArray) realmAccess.get("roles"); return roles.stream() .map(Object::toString) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } return Collections.emptySet(); } }
3. 修改spring.security.oauth2.client相关认证逻辑说明
WebFlux环境下,spring.security.oauth2.client自动配置的是ReactiveOAuth2AuthorizedClientManager而非传统AuthenticationManager。如需自定义客户端认证逻辑,可通过以下方式调整:
注册自定义ReactiveOAuth2AuthorizedClientManager覆盖默认配置:
@Bean public ReactiveOAuth2AuthorizedClientManager authorizedClientManager( ReactiveClientRegistrationRepository clientRegistrationRepository, ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder() .authorizationCode() .refreshToken() .build(); DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
内容的提问来源于stack exchange,提问作者samibe

