You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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]]

问题根源分析

  1. REST服务Security配置错误:oauth2Login()是针对用户登录的授权码模式配置,不适用于机器对机器的client_credentials场景,该配置会引导未认证请求跳转至登录页,而非验证请求头中的Bearer Token。
  2. 客户端调用的Registration ID不匹配:客户端配置的Registration ID为my-service,但调用代码中指定的是catalogue-services,可能导致Token未正确携带至请求头。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 13:15:06