Spring Shell CLI应用Keycloak用户认证及令牌安全存储方案咨询
Spring Shell对接Keycloak认证的实用方案
一、简化认证管理:基于Spring Security OAuth2 Client的原生支持
Spring Security OAuth2 Client完全支持非Web(CLI)场景的认证,无需手动实现令牌获取/刷新逻辑,推荐两种适配CLI的授权流程:
1. PKCE增强的Authorization Code流程(推荐,符合OAuth2最佳实践)
适用于公开客户端(无需client-secret),通过PKCE避免授权码劫持。
- 添加依赖:
<!-- Maven --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency> - 配置
application.yml:spring: security: oauth2: client: registration: keycloak-cli: client-id: your-cli-client-id authorization-grant-type: authorization_code redirect-uri: http://localhost:8080/login/oauth2/code/keycloak-cli # CLI监听本地端口接收回调 provider: keycloak-cli: issuer-uri: https://your-keycloak-server/auth/realms/your-realm - 用
OAuth2AuthorizedClientManager自动管理令牌:
该组件自动处理令牌获取、过期刷新,直接注入到Feign拦截器即可:
首次认证时,CLI会自动打开浏览器引导用户登录Keycloak,后续自动复用令牌。@Component public class OAuth2FeignInterceptor implements RequestInterceptor { private final OAuth2AuthorizedClientManager clientManager; public OAuth2FeignInterceptor(OAuth2AuthorizedClientManager clientManager) { this.clientManager = clientManager; } @Override public void apply(RequestTemplate template) { OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId("keycloak-cli") .principal(SecurityContextHolder.getContext().getAuthentication()) .build(); OAuth2AuthorizedClient client = clientManager.authorize(request); if (client != null) { template.header(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken().getTokenValue()); } } }
2. Password流程(仅信任内部CLI应用使用)
如果需要直接在CLI中输入账号密码完成认证,可配置Password授权类型:
- 修改配置:
spring: security: oauth2: client: registration: keycloak-cli: client-id: your-cli-client-id client-secret: your-client-secret # 保密客户端需配置 authorization-grant-type: password username: ${user.username:} # 可通过CLI参数传入 password: ${user.password:} - 拦截器中传入用户认证信息:
// 构造用户认证对象 Authentication principal = new UsernamePasswordAuthenticationToken(username, password); OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId("keycloak-cli") .principal(principal) .build();
二、令牌的安全存储方案
1. 数据库加密存储(生产环境首选)
用Spring Security提供的JdbcOAuth2AuthorizedClientService将令牌存储到数据库,同时加密敏感字段:
- 创建存储表(Spring Security默认结构):
CREATE TABLE oauth2_authorized_client ( client_registration_id VARCHAR(100) NOT NULL, principal_name VARCHAR(200) NOT NULL, access_token_type VARCHAR(100) NOT NULL, access_token_value CLOB NOT NULL, access_token_issued_at TIMESTAMP NOT NULL, access_token_expires_at TIMESTAMP NOT NULL, access_token_scopes VARCHAR(1000) DEFAULT NULL, refresh_token_value CLOB DEFAULT NULL, refresh_token_issued_at TIMESTAMP DEFAULT NULL, PRIMARY KEY (client_registration_id, principal_name) ); - 注册加密版的客户端服务:
@Bean public OAuth2AuthorizedClientService authorizedClientService(DataSource dataSource, ClientRegistrationRepository repo, TextEncryptor encryptor) { JdbcOAuth2AuthorizedClientService service = new JdbcOAuth2AuthorizedClientService(dataSource, repo); service.setAccessTokenRowMapper(new EncryptedAccessTokenRowMapper(encryptor)); service.setRefreshTokenRowMapper(new EncryptedRefreshTokenRowMapper(encryptor)); return service; } // 自定义加密行映射器,实现令牌字段的加密/解密 static class EncryptedAccessTokenRowMapper extends JdbcOAuth2AuthorizedClientService.AccessTokenRowMapper { private final TextEncryptor encryptor; // 实现加密逻辑 }
2. 本地加密文件存储
如果无需数据库,可将令牌加密后存储到用户本地目录(如~/.your-app/tokens):
- 核心逻辑示例:
密钥可通过CLI启动参数传入,或从环境变量读取。public class LocalTokenStorage { private static final String STORAGE_FILE = System.getProperty("user.home") + "/.your-app/encrypted-tokens.dat"; private final TextEncryptor encryptor; private final ObjectMapper objectMapper = new ObjectMapper(); public LocalTokenStorage(TextEncryptor encryptor) { this.encryptor = encryptor; } public void saveToken(OAuth2AccessToken token) throws IOException { String tokenJson = objectMapper.writeValueAsString(token); String encrypted = encryptor.encrypt(tokenJson); Files.write(Paths.get(STORAGE_FILE), encrypted.getBytes(StandardCharsets.UTF_8)); } public OAuth2AccessToken loadToken() throws IOException { String encrypted = Files.readString(Paths.get(STORAGE_FILE), StandardCharsets.UTF_8); String tokenJson = encryptor.decrypt(encrypted); return objectMapper.readValue(tokenJson, OAuth2AccessToken.class); } }
3. 系统原生密钥管理服务(最高安全级别)
借助操作系统的密钥存储服务,无需手动处理加密:
- macOS:存储到Keychain,可使用
com.github.javakeyring:java-keyring库简化调用 - Windows:使用DPAPI加密或Credential Manager存储
- Linux:利用libsecret/KWallet
示例(java-keyring):
Keyring keyring = Keyring.create(); // 存储令牌 keyring.setPassword("your-app-name", "access-token", tokenValue); // 读取令牌 String token = keyring.getPassword("your-app-name", "access-token");
总结
- 优先采用Spring Security OAuth2 Client的原生方案,避免手动实现令牌逻辑,减少出错概率
- 令牌存储根据场景选择:生产环境用数据库加密或系统密钥服务,开发测试可临时用内存存储
内容的提问来源于stack exchange,提问作者someone
相关产品推荐
相关产品推荐

