上层应用如何为其所依赖的基础SDK里的静态属性赋值?,为了提高合作商户侧A
分享于 点击 46803 次 点评:267
上层应用如何为其所依赖的基础SDK里的静态属性赋值?,为了提高合作商户侧A
我们的系统对商户暴露了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
类的全限定名称。
优点
- 完全解耦,符合开闭原则
- 支持多实现动态选择
缺点
- 实现稍复杂
- 需熟悉 SPI 机制
方案 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 使用场景和技术栈,选择最合适的方案即可确保密钥安全注入。
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/18911285
用户点评