上层应用如何为其所依赖的基础SDK里的静态属性赋值?
<p>我们的系统对商户暴露了RestAPI,供合作商户以API的形式接入。为了提高合作商户侧API接入的开发效率,我编写了一个SDK。</p><p>下面 <code>ClientApiUtils</code>是这个SDK一个工具类,封装了API数据加解密、API数字签名的工具方法。这些工具方法都是静态方法。在这个 <code>ClientApiUtils</code>中,有两个静态field,<code>platformPrivateKey</code>和<code>platformPublicKey</code>,分别是我们系统的数字签名RSA公私钥。</p>
<pre><code class="language-java">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){...}
...
}
</code></pre>
<p><code>platformPrivateKey</code>和<code>platformPublicKey</code>这两个field的值,需要被依赖的上层应用来赋值。</p>
<p>那么,要实现 SDK 中的 <code>ClientApiUtils</code> 工具类能够从上层应用获取 <code>platformPrivateKey</code> 和 <code>platformPublicKey</code> 的值,以下是几种推荐的实现方案:</p>
<hr>
<h3 id="方案-1静态初始化方法推荐"><strong>方案 1:静态初始化方法(推荐)</strong></h3>
<h4 id="实现方式"><strong>实现方式</strong></h4>
<pre><code class="language-java">@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;
}
...
}
</code></pre>
<h4 id="上层应用调用"><strong>上层应用调用</strong></h4>
<pre><code class="language-java">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);
}
</code></pre>
<h4 id="优点"><strong>优点</strong></h4>
<ul>
<li>简单直接,无第三方依赖</li>
<li>明确控制初始化时机</li>
</ul>
<h4 id="缺点"><strong>缺点</strong></h4>
<ul>
<li>需手动调用,依赖开发者主动初始化</li>
</ul>
<hr>
<h3 id="方案-2配置文件注入"><strong>方案 2:配置文件注入</strong></h3>
<h4 id="实现方式-1"><strong>实现方式</strong></h4>
<pre><code class="language-java">@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);
}
}
}
</code></pre>
<h4 id="配置文件-sdk-configproperties"><strong>配置文件 (<code>sdk-config.properties</code>)</strong></h4>
<pre><code class="language-properties">platform.private.key=MIIEvQ...
platform.public.key=MIIBI...
</code></pre>
<h4 id="优点-1"><strong>优点</strong></h4>
<ul>
<li>配置与代码分离</li>
<li>支持热更新(需重载配置)</li>
</ul>
<h4 id="缺点-1"><strong>缺点</strong></h4>
<ul>
<li>需约定配置文件路径</li>
<li>不适用于动态密钥场景</li>
</ul>
<hr>
<h3 id="方案-3spi-机制面向接口"><strong>方案 3:SPI 机制(面向接口)</strong></h3>
<h4 id="步骤-1定义密钥提供接口"><strong>步骤 1:定义密钥提供接口</strong></h4>
<pre><code class="language-java">package com.zfquan.clientapi.sdk;
public interface KeyProvider {
String getPrivateKey();
String getPublicKey();
}
</code></pre>
<h4 id="步骤-2工具类通过-spi-获取密钥"><strong>步骤 2:工具类通过 SPI 获取密钥</strong></h4>
<pre><code class="language-java">@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();
// ...
}
}
</code></pre>
<h4 id="步骤-3上层应用实现接口"><strong>步骤 3:上层应用实现接口</strong></h4>
<pre><code class="language-java">// 在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..."; }
}
</code></pre>
<h4 id="步骤-4文件注册">**步骤 4:文件注册</h4>
<p>创建文件<br>
src/main/resources/META-INF/services/com.zfquan.clientapi.sdk.KeyProvider,文件内容就是步骤3中<code>AppKeyProvider</code>类的全限定名称。</p>
<h4 id="优点-2"><strong>优点</strong></h4>
<ul>
<li>完全解耦,符合开闭原则</li>
<li>支持多实现动态选择</li>
</ul>
<h4 id="缺点-2"><strong>缺点</strong></h4>
<ul>
<li>实现稍复杂</li>
<li>需熟悉 SPI 机制</li>
</ul>
<hr>
<h3 id="方案-4spring-aware-集成适合-spring-项目"><strong>方案 4:Spring Aware 集成(适合 Spring 项目)</strong></h3>
<h4 id="步骤-1创建-spring-上下文感知类"><strong>步骤 1:创建 Spring 上下文感知类</strong></h4>
<pre><code class="language-java">@Component
public class SdkKeyInjector implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext ctx) {
KeyConfig config = ctx.getBean(KeyConfig.class);
ClientApiUtils.init(config.getPrivateKey(), config.getPublicKey());
}
}
</code></pre>
<h4 id="步骤-2上层应用配置密钥"><strong>步骤 2:上层应用配置密钥</strong></h4>
<pre><code class="language-java">@Configuration
public class KeyConfig {
@Value("${platform.private.key}")
private String privateKey;
@Value("${platform.public.key}")
private String publicKey;
// Getter省略
}
</code></pre>
<h4 id="优点-3"><strong>优点</strong></h4>
<ul>
<li>天然集成 Spring 生态</li>
<li>支持 Spring 配置方式(properties/YAML)</li>
</ul>
<h4 id="缺点-3"><strong>缺点</strong></h4>
<ul>
<li>强依赖 Spring 框架</li>
<li>非 Spring 项目不可用</li>
</ul>
<hr>
<h3 id="方案-5动态回调机制"><strong>方案 5:动态回调机制</strong></h3>
<h4 id="实现方式-2"><strong>实现方式</strong></h4>
<pre><code class="language-java">@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();
}
}
</code></pre>
<h4 id="上层应用实现"><strong>上层应用实现</strong></h4>
<pre><code class="language-java">// 应用启动时注册
@PostConstruct
public void setupSDK() {
ClientApiUtils.registerKeyLoader(new KeyLoader() {
@Override
public String getPrivateKey() { return "MIIEvQ..."; }
@Override
public String getPublicKey() { return "MIIBI..."; }
});
}
</code></pre>
<h4 id="优点-4"><strong>优点</strong></h4>
<ul>
<li>灵活支持动态密钥</li>
<li>无框架依赖</li>
</ul>
<h4 id="缺点-4"><strong>缺点</strong></h4>
<ul>
<li>需手动实现回调</li>
</ul>
<hr>
<p><br><br></p>
<h3 id="各方案对比"><strong>各方案对比</strong></h3>
<table>
<thead>
<tr>
<th><strong>方案</strong></th>
<th><strong>适用场景</strong></th>
<th><strong>复杂度</strong></th>
<th><strong>灵活性</strong></th>
<th><strong>框架依赖</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>静态初始化方法</td>
<td>简单应用</td>
<td>★☆☆☆☆</td>
<td>★★☆☆☆</td>
<td>无</td>
</tr>
<tr>
<td>配置文件注入</td>
<td>配置驱动型应用</td>
<td>★★☆☆☆</td>
<td>★★★☆☆</td>
<td>无</td>
</tr>
<tr>
<td>SPI 机制</td>
<td>需要扩展性的SDK</td>
<td>★★★★☆</td>
<td>★★★★★</td>
<td>无</td>
</tr>
<tr>
<td>Spring Aware 集成</td>
<td>Spring Boot 项目</td>
<td>★★★☆☆</td>
<td>★★★★☆</td>
<td>强依赖</td>
</tr>
<tr>
<td>动态回调机制</td>
<td>需要运行时动态获取密钥</td>
<td>★★★☆☆</td>
<td>★★★★★</td>
<td>无</td>
</tr>
</tbody>
</table>
<h4 id="方案推荐"><strong>方案推荐</strong></h4>
<ul>
<li><strong>通用 SDK</strong> → 选择 <strong>SPI 机制</strong>(方案3),提供标准扩展接口</li>
<li><strong>Spring 项目</strong> → 选择 <strong>Spring Aware 集成</strong>(方案4),无缝融入生态</li>
<li><strong>快速实现</strong> → 选择 <strong>静态初始化方法</strong>(方案1),简单高效</li>
</ul>
<hr>
<p><br><br></p>
<h3 id="秘钥存储安全建议"><strong>秘钥存储安全建议</strong></h3>
<ol>
<li><strong>密钥存储</strong>:<pre><code class="language-java">// 避免硬编码,使用安全存储
private String privateKey = System.getenv("PLATFORM_PRIVATE_KEY");
</code></pre>
</li>
<li><strong>访问控制</strong>:<pre><code class="language-java">// 限制密钥访问权限
SecurityManager manager = System.getSecurityManager();
if (manager != null) manager.checkPermission(new SDKKeyPermission());
</code></pre>
</li>
<li><strong>密钥轮换</strong>:<pre><code class="language-java">// SPI实现支持动态更新
public class DynamicKeyProvider implements KeyProvider {
public String getPublicKey() {
return KeyVault.getCurrentKey(); // 从密钥管理系统获取
}
}
</code></pre>
</li>
</ol>
<hr>
<p><br><br></p>
<p>以上,商户可根据 SDK 使用场景和技术栈,选择最合适的方案即可确保密钥安全注入。</p>
</div>
<div id="MySignature" role="contentinfo">
<hr class="signhr"><p style="text-indent:2em;font-size:12px;text-align:center">当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge<br>本文来自博客园,转载请注明原文链接:https://www.cnblogs.com/buguge/p/18911285</p><hr class="signhr">
<style>hr.signhr{width:80%;margin:0 auto;border: 0;height: 4px;background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0))}</style><br><br>
来源:https://www.cnblogs.com/buguge/p/18911285
頁:
[1]