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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:34:54