Spring Boot 2.7集成OIDC Bearer Token始终重定向至登录页问题
Spring Boot + Keycloak 机器对机器OIDC集成问题解决
问题场景
无UI的Spring Boot客户端应用,采用client_credentials授权模式对接Keycloak,已确认能成功获取Token,但调用受OIDC保护的REST服务时始终被重定向至登录页。REST服务使用新版Spring Security模型(未用废弃的Keycloak适配器及WebSecurityConfigurerAdapter),调试发现请求被识别为匿名认证,Token中的Realm角色与客户端角色未被正确拾取。
客户端配置
application.yml
spring: security: oauth2: client: registration: my-service: client-id: client-id client-secret: client-secret authorization-grant-type: client_credentials provider: my-service: token-uri: https://mytokenhost.mydomain/token
WebClient Bean配置
@Bean public WebClient webClient(final OAuth2AuthorizedClientManager authorizedClientManager) { final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauth2Client.setDefaultClientRegistrationId("my-service"); return WebClient.builder() .apply(oauth2Client.oauth2Configuration()) .build(); }
OAuth2AuthorizedClientManager Bean配置
@Bean public OAuth2AuthorizedClientManager authorizedClientManager( final ClientRegistrationRepository clientRegistrationRepository, final OAuth2AuthorizedClientService authorizedClientService) { final OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder().refreshToken().clientCredentials().build(); final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientService); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); return authorizedClientManager; }
服务调用代码
final String s = webClient .method(HttpMethod.GET) .uri("http://localhost:8080/my-rest-service/service?param1=value") .attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId( "catalogue-services")) .retrieve() .bodyToMono(String.class) .block();
REST服务配置
application.yml
spring: security: oauth2: client: registration: my-service: client-id: client-id client-secret: my-secret authorization-grant-type: client_credentials provider: my-service: token-uri: https://mytokenhost.mydomain/token/openid-connect/token
SecurityFilterChain配置
@Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { http.authorizeRequests( a -> a.antMatchers("/", "/error", "/someUrl") .permitAll() .anyRequest() .authenticated()) .oauth2Login(); return http.build(); }
调试信息
AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
问题根源分析
- REST服务Security配置错误:
oauth2Login()是针对用户登录的授权码模式配置,不适用于机器对机器的client_credentials场景,该配置会引导未认证请求跳转至登录页,而非验证请求头中的Bearer Token。 - 客户端调用的Registration ID不匹配:客户端配置的Registration ID为
my-service,但调用代码中指定的是catalogue-services,可能导致Token未正确携带至请求头。 - REST服务配置定位错误:作为资源服务器,无需配置OAuth2 Client的注册信息,应配置资源服务器相关的JWT验证参数。
解决方案
1. 修正REST服务的Security配置
替换oauth2Login()为oauth2ResourceServer().jwt(),配置资源服务器验证JWT Token,并可自定义转换器提取Keycloak角色:
@Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { http.authorizeRequests( a -> a.antMatchers("/", "/error", "/someUrl") .permitAll() .anyRequest() .authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))); return http.build(); } // 自定义JWT转换器,提取Keycloak的Realm角色 @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 指向Keycloak Token中存储Realm角色的字段 grantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access"); grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return jwtAuthenticationConverter; }
2. 修正REST服务的application.yml配置
删除无效的OAuth2 Client配置,添加资源服务器的JWT验证参数:
spring: security: oauth2: resourceserver: jwt: issuer-uri: https://mytokenhost.mydomain/realms/你的Realm名称 jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
3. 修正客户端调用代码的Registration ID
将调用代码中的catalogue-services改为与客户端配置一致的my-service:
final String s = webClient .method(HttpMethod.GET) .uri("http://localhost:8080/my-rest-service/service?param1=value") .attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId( "my-service")) .retrieve() .bodyToMono(String.class) .block();
4. 验证Token携带
启动客户端与服务端,通过调试日志确认请求头中是否包含Authorization: Bearer <Token>,确保Token正确传递。
内容的提问来源于stack exchange,提问作者user1
相关产品推荐
相关产品推荐

