如何在Spring Boot Shell应用中实现OAuth2认证
实现Spring Boot Shell应用通过Resource Owner Password Flow获取Keycloak Token
1. 添加依赖
在pom.xml中引入Spring Security OAuth2相关依赖,用于支持密码授权流程和HTTP请求:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <!-- 若使用WebClient则需引入,用RestTemplate可替换为spring-boot-starter-web --> </dependency> </dependencies>
2. 配置Keycloak与OAuth2参数
在application.yml中配置Keycloak的token端点、客户端信息及授权类型:
spring: security: oauth2: client: registration: keycloak: client-id: ${YOUR_CLIENT_ID} client-secret: ${YOUR_CLIENT_SECRET} authorization-grant-type: password scope: openid, phone provider: keycloak: token-uri: ${YOUR_REALM_URI}/protocol/openid-connect/token user-name-attribute: preferred_username
注意:需提前在Keycloak后台将目标客户端的Access Type设为
confidential,并开启Resource Owner Password Credentials Grant授权类型。
3. 配置OAuth2客户端管理器
创建配置类,初始化支持密码授权的客户端管理器,同时支持自动刷新token:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; @Configuration public class OAuth2Config { @Bean public OAuth2AuthorizedClientManager authorizedClientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { OAuth2AuthorizedClientProvider provider = OAuth2AuthorizedClientProviderBuilder.builder() .password() .refreshToken() .build(); DefaultOAuth2AuthorizedClientManager manager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository); manager.setAuthorizedClientProvider(provider); return manager; } }
4. 实现Token获取与API请求
方式一:手动获取Token并发起请求
创建Token服务类,通过客户端管理器获取access token:
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.stereotype.Component; @Component public class TokenService { private final OAuth2AuthorizedClientManager authorizedClientManager; public TokenService(OAuth2AuthorizedClientManager authorizedClientManager) { this.authorizedClientManager = authorizedClientManager; } public String getAccessToken(String username, String password) { OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId("keycloak") .principal(username) .attrs(attrs -> attrs.put(OAuth2AuthorizedClientManager.PASSWORD_ATTRIBUTE_NAME, password)) .build(); OAuth2AuthorizedClient client = authorizedClientManager.authorize(request); return client.getAccessToken().getTokenValue(); } }
在Shell命令类中调用Token服务,携带token发起API请求:
import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; import org.springframework.web.client.RestTemplate; @ShellComponent public class ApiCommand { private final TokenService tokenService; private final RestTemplate restTemplate; public ApiCommand(TokenService tokenService) { this.tokenService = tokenService; this.restTemplate = new RestTemplate(); } @ShellMethod("调用受Keycloak保护的API") public String callProtectedApi(String username, String password, String apiUrl) { String token = tokenService.getAccessToken(username, password); return restTemplate.getForObject(apiUrl, String.class, req -> { req.getHeaders().setBearerAuth(token); return req; }); } }
方式二:用WebClient自动携带Token
配置WebClient,使其自动从客户端管理器获取并携带token:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; @Configuration public class WebClientConfig { @Bean public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauthFilter = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); oauthFilter.setDefaultClientRegistrationId("keycloak"); return WebClient.builder() .apply(oauthFilter.oauth2Configuration()) .build(); } }
在Shell命令类中使用WebClient发起请求:
import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; import org.springframework.web.reactive.function.client.WebClient; import java.util.Collections; @ShellComponent public class ApiWebClientCommand { private final WebClient webClient; public ApiWebClientCommand(WebClient webClient) { this.webClient = webClient; } @ShellMethod("通过WebClient调用受保护API") public String callApiWithWebClient(String username, String password, String apiUrl) { // 构造临时认证上下文,用于OAuth2授权 OAuth2User oauth2User = new DefaultOAuth2User(Collections.emptyList(), Collections.singletonMap("username", username), "username"); Authentication auth = new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(oauth2User, password, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(auth); return webClient.get() .uri(apiUrl) .retrieve() .bodyToMono(String.class) .block(); } }
注意事项
- Resource Owner Password Flow存在直接处理用户密码的安全风险,若场景允许(比如服务间调用),优先考虑Client Credentials Flow。
- 确保Keycloak客户端的scope配置与请求中一致,否则可能获取到权限不足的token。
内容的提问来源于stack exchange,提问作者Jayser
相关产品推荐
相关产品推荐

