我们的系统对商户暴露了RestAPI,供合作商户以API的形式接入。为了提高合作商户侧API接入的开发效率,我编写了一个SDK。
下面 ClientApiUtils是这个SDK一个工具类,封装了API数据加解密、API数字签名的工具方法。这些工具方法都是静态方法。在这个 ClientApiUtils中,有两个静态field,platformPrivateKey和platformPublicKey,分别是我们系统的数字签名RSA公私钥。- package com.zfquan.clientapi.sdk.common;
- public final class ClientApiUtils {
- private static String platformPrivateKey;
- private static String platformPublicKey;
- // 加密 及 签名 --- client端请求我方api时调用
- public static void encryptThenSign(LevyRequestBase requestBase, String encryptKey, String privateKey) {...}
- // 加密 及 签名 --- 我方主动发起通知请求时调用
- public static void encryptThenSignUsingPlatformSignKey(LevyRequestBase requestBase, String encryptKey){...}
- // 验签及解密--- 我方接收到client端请求后的验签
- public static void verifySignThenDecrypt(LevyRequestBase requestBase, String publicKey, String encryptKey){...}
- ...
- }
复制代码 platformPrivateKey和platformPublicKey这两个field的值,需要被依赖的上层应用来赋值。
那么,要实现 SDK 中的 ClientApiUtils 工具类能够从上层应用获取 platformPrivateKey 和 platformPublicKey 的值,以下是几种推荐的实现方案:
方案 1:静态初始化方法(推荐)
实现方式
- @Slf4j
- public final class ClientApiUtils {
- private static String platformPrivateKey;
- private static String platformPublicKey;
-
- // 初始化方法(需上层应用显式调用)
- public static void initPlatformKey(String platformKey, KeyType keyType) {
- if (keyType == KeyType.PUBLIC_KEY)
- ClientApiUtils.platformPublicKey = platformKey;
- else
- ClientApiUtils.platformPrivateKey = platformKey;
- }
-
- ...
- }
复制代码 上层应用调用
- package com.zfquan.config;
- @Configuration
- @Data
- public class CommonConfig {
- @Value("${platform.api.privateKey:}")
- private String platformPrivateKey;
- @Value("${platform.api.publicKey:}")
- private String platformPublicKey;
- // 在应用启动时初始化
- @PostConstruct
- public void init() {
- ClientApiUtils.initPlatformKey(platformPrivateKey, KeyType.PRIVATE_KEY);
- ClientApiUtils.initPlatformKey(platformPublicKey, KeyType.PUBLIC_KEY);
- }
复制代码 优点
缺点
方案 2:配置文件注入
实现方式
- @Slf4j
- public final class ClientApiUtils {
- private static String platformPrivateKey;
- private static String platformPublicKey;
-
- static {
- try (InputStream is = ClientApiUtils.class.getResourceAsStream("/sdk-config.properties")) {
- Properties props = new Properties();
- props.load(is);
- platformPrivateKey = props.getProperty("platform.private.key");
- platformPublicKey = props.getProperty("platform.public.key");
- } catch (IOException e) {
- throw new RuntimeException("SDK配置加载失败", e);
- }
- }
- }
复制代码 配置文件 (sdk-config.properties)
- platform.private.key=MIIEvQ...
- platform.public.key=MIIBI...
复制代码 优点
缺点
方案 3:SPI 机制(面向接口)
步骤 1:定义密钥提供接口
- package com.zfquan.clientapi.sdk;
- public interface KeyProvider {
- String getPrivateKey();
- String getPublicKey();
- }
复制代码 步骤 2:工具类通过 SPI 获取密钥
- @Slf4j
- public final class ClientApiUtils {
- private static final KeyProvider keyProvider;
-
- static {
- ServiceLoader<KeyProvider> loader = ServiceLoader.load(KeyProvider.class);
- keyProvider = loader.findFirst().orElseThrow(() ->
- new IllegalStateException("未实现KeyProvider接口")
- );
- }
-
- public static void verifySign(...) {
- String pubKey = keyProvider.getPublicKey();
- // ...
- }
- }
复制代码 步骤 3:上层应用实现接口
- // 在META-INF/services/com.zfquan.clientapi.sdk.KeyProvider文件中注册
- public class AppKeyProvider implements KeyProvider {
- @Override
- public String getPrivateKey() { return "MIIEvQ..."; }
-
- @Override
- public String getPublicKey() { return "MIIBI..."; }
- }
复制代码 **步骤 4:文件注册
创建文件
src/main/resources/META-INF/services/com.zfquan.clientapi.sdk.KeyProvider,文件内容就是步骤3中AppKeyProvider类的全限定名称。
优点
缺点
方案 4:Spring Aware 集成(适合 Spring 项目)
步骤 1:创建 Spring 上下文感知类
- @Component
- public class SdkKeyInjector implements ApplicationContextAware {
- @Override
- public void setApplicationContext(ApplicationContext ctx) {
- KeyConfig config = ctx.getBean(KeyConfig.class);
- ClientApiUtils.init(config.getPrivateKey(), config.getPublicKey());
- }
- }
复制代码 步骤 2:上层应用配置密钥
- @Configuration
- public class KeyConfig {
- @Value("${platform.private.key}")
- private String privateKey;
-
- @Value("${platform.public.key}")
- private String publicKey;
-
- // Getter省略
- }
复制代码 优点
- 天然集成 Spring 生态
- 支持 Spring 配置方式(properties/YAML)
缺点
- 强依赖 Spring 框架
- 非 Spring 项目不可用
方案 5:动态回调机制
实现方式
- @Slf4j
- public final class ClientApiUtils {
- private static KeyLoader keyLoader;
-
- // 注册密钥加载器
- public static void registerKeyLoader(KeyLoader loader) {
- keyLoader = loader;
- }
-
- public static void verifySign(...) {
- String pubKey = keyLoader.getPublicKey();
- // ...
- }
-
- public interface KeyLoader {
- String getPrivateKey();
- String getPublicKey();
- }
- }
复制代码 上层应用实现
- // 应用启动时注册
- @PostConstruct
- public void setupSDK() {
- ClientApiUtils.registerKeyLoader(new KeyLoader() {
- @Override
- public String getPrivateKey() { return "MIIEvQ..."; }
-
- @Override
- public String getPublicKey() { return "MIIBI..."; }
- });
- }
复制代码 优点
缺点
各方案对比
方案适用场景复杂度灵活性框架依赖静态初始化方法简单应用★☆☆☆☆★★☆☆☆无配置文件注入配置驱动型应用★★☆☆☆★★★☆☆无SPI 机制需要扩展性的SDK★★★★☆★★★★★无Spring Aware 集成Spring Boot 项目★★★☆☆★★★★☆强依赖动态回调机制需要运行时动态获取密钥★★★☆☆★★★★★无方案推荐
- 通用 SDK → 选择 SPI 机制(方案3),提供标准扩展接口
- Spring 项目 → 选择 Spring Aware 集成(方案4),无缝融入生态
- 快速实现 → 选择 静态初始化方法(方案1),简单高效
秘钥存储安全建议
- 密钥存储:
- // 避免硬编码,使用安全存储
- private String privateKey = System.getenv("PLATFORM_PRIVATE_KEY");
复制代码 - 访问控制:
- // 限制密钥访问权限
- SecurityManager manager = System.getSecurityManager();
- if (manager != null) manager.checkPermission(new SDKKeyPermission());
复制代码 - 密钥轮换:
- // SPI实现支持动态更新
- public class DynamicKeyProvider implements KeyProvider {
- public String getPublicKey() {
- return KeyVault.getCurrentKey(); // 从密钥管理系统获取
- }
- }
复制代码
以上,商户可根据 SDK 使用场景和技术栈,选择最合适的方案即可确保密钥安全注入。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |