如何用技术用户身份认证API调用(Spring Security+Keycloak)
解决方案:使用技术用户身份调用API
要实现以技术用户身份发起API请求,核心是通过Spring Security的OAuth2AuthorizedClientManager获取技术用户的访问令牌,并通过请求拦截器将令牌添加到请求头中,与当前登录用户的上下文隔离。
步骤1:修正Keycloak客户端配置
首先检查并修正你的YAML配置,确保技术用户的客户端信息正确(password模式需要区分客户端ID/密钥和技术用户账号/密码):
spring: security: oauth2: client: provider: keycloak: authorization-uri: ${keycloak.base_url}/auth token-uri: ${keycloak.base_url}/token jwk-set-uri: ${keycloak.base_url}/certs registration: # 用户登录用的授权码模式客户端 user: provider: keycloak client-id : ${keycloak.client.id} client-secret: ${keycloak.client.secret} authorization-grant-type: authorization_code redirectUri: '{baseUrl}/login/oauth2/code/{registrationId}' scope: - openid - roles - institution # 技术用户用的密码模式客户端 tech: provider: keycloak client-id : ${keycloak.tech.client-id} # Keycloak中创建的客户端ID client-secret: ${keycloak.tech.client-secret} # 客户端的密钥 authorization-grant-type: password scope: # 按需添加技术用户所需的权限范围 - openid - roles
在配置文件(如application.properties)中补充技术用户的账号密码:
keycloak.tech.username=你的技术用户账号 keycloak.tech.password=你的技术用户密码
步骤2:配置技术用户的OAuth2授权客户端管理器
创建OAuth2AuthorizedClientManager Bean,专门用于获取技术用户的授权令牌,自动处理令牌获取和刷新:
import org.springframework.security.oauth2.client.*; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class TechUserOAuth2Config { @Bean public OAuth2AuthorizedClientManager techUserAuthorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService, @Value("${keycloak.tech.username}") String techUsername, @Value("${keycloak.tech.password}") String techPassword) { DefaultOAuth2AuthorizedClientManager manager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService); // 设置密码模式所需的用户凭证 manager.setContextAttributesMapper(contextAttributes -> { Map<String, Object> attrs = new HashMap<>(); attrs.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, techUsername); attrs.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, techPassword); return attrs; }); return manager; } }
步骤3:创建技术用户认证拦截器
编写请求拦截器,将技术用户的访问令牌添加到API请求的Authorization头中:
import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Collections; @Component public class TechUserAuthInterceptor implements ClientHttpRequestInterceptor { private final OAuth2AuthorizedClientManager techUserAuthorizedClientManager; public TechUserAuthInterceptor(OAuth2AuthorizedClientManager techUserAuthorizedClientManager) { this.techUserAuthorizedClientManager = techUserAuthorizedClientManager; } @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 构建授权请求,指定技术用户的客户端注册ID为"tech" OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId("tech") .principal(new AnonymousAuthenticationToken("tech", "tech-service", Collections.emptyList())) .build(); // 获取授权客户端(自动处理令牌获取/刷新) OAuth2AuthorizedClient authorizedClient = techUserAuthorizedClientManager.authorize(authorizeRequest); if (authorizedClient == null || authorizedClient.getAccessToken() == null) { throw new IllegalStateException("无法获取技术用户的访问令牌"); } // 添加Bearer令牌到请求头 request.getHeaders().setBearerAuth(authorizedClient.getAccessToken().getTokenValue()); return execution.execute(request, body); } }
步骤4:创建技术用户专用的RestClient
单独创建一个RestClient Bean,绑定上述拦截器,确保与用户登录上下文隔离:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler; import org.springframework.web.client.RestClient; import org.apache.http.impl.client.HttpClients; @Configuration public class TechRestClientConfig { @Bean public RestClient techUserRestClient(TechUserAuthInterceptor techAuthInterceptor, @Value("${keycloak.api.base_url}") String apiUrl) { return RestClient.builder() .messageConverters(converters -> { converters.clear(); converters.add(new MappingJackson2HttpMessageConverter()); }) .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler()) .requestFactory(new HttpComponentsClientHttpRequestFactory(HttpClients.createDefault())) .baseUrl(apiUrl) .requestInterceptor(techAuthInterceptor) // 绑定技术用户认证拦截器 .build(); } }
步骤5:使用技术用户RestClient调用API
在需要以技术用户身份调用API的服务类中,注入techUserRestClient即可:
import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import org.springframework.http.MediaType; @Service public class TechApiService { private final RestClient techUserRestClient; public TechApiService(RestClient techUserRestClient) { this.techUserRestClient = techUserRestClient; } public ResultClass callTechApi() { return techUserRestClient.get() .uri("/somePath") .accept(MediaType.APPLICATION_JSON) .retrieve() .body(ResultClass.class); } }
关键说明
- 技术用户的认证流程与当前登录用户完全隔离,不会干扰SecurityContext中的用户信息。
DefaultOAuth2AuthorizedClientManager会自动处理令牌的过期刷新,无需手动维护令牌生命周期。- 确保Keycloak中已为技术用户分配足够的权限,允许调用目标API(包括Keycloak自身服务)。
内容的提问来源于stack exchange,提问作者daniu
相关产品推荐
相关产品推荐

