如何在Spring项目中读取OpenShift Secret私钥生成JWT
在OpenShift中读取Secret私钥用于Spring项目JWT生成
背景信息
我在OpenShift中有如下Secret资源:
kind: Secret apiVersion: v1 metadata: name: jwt-signing-keys namespace: test-dev uid: xxxx-xxxxx-xxxxx resourceVersion: '0000000000' creationTimestamp: '2022-07-19T07:55:04Z' managedFields: - manager: Mozilla operation: Update apiVersion: v1 time: '2022-07-19T07:55:04Z' fieldsType: FieldsV1 fieldsV1: 'f:data': .: {} 'f:private-key': {} 'f:public-key': {} 'f:type': {} data: private-key: xxxxx public-key: xxxxx type: Opaque
当前Spring项目通过以下类从本地资源读取私钥,需要修改为读取上述OpenShift Secret:
@Component @RequiredArgsConstructor public class JwtKeyProvider { private final ResourceUtil resourceUtil; private final Base64Util base64Util; @Getter private PrivateKey privateKey; @PostConstruct public void init() { privateKey = readKey( "classpath:keys/private-key.pkcs8", "PRIVATE", this::privateKeySpec, this::privateKeyGenerator ); } private <T extends Key> T readKey(String resourcePath, String headerSpec, Function<String, EncodedKeySpec> keySpec, BiFunction<KeyFactory, EncodedKeySpec, T> keyGenerator) { try { String keyString = resourceUtil.asString(resourcePath); //TODO you can check the headers and throw an exception here if you want keyString = keyString .replace("-----BEGIN " + headerSpec + " KEY-----", "") .replace("-----END " + headerSpec + " KEY-----", "") .replaceAll("\\s+", ""); return keyGenerator.apply(KeyFactory.getInstance("RSA"), keySpec.apply(keyString)); } catch(NoSuchAlgorithmException | IOException e) { throw new JwtInitializationException(e); } } private EncodedKeySpec privateKeySpec(String data) { return new PKCS8EncodedKeySpec(base64Util.decode(data)); } private PrivateKey privateKeyGenerator(KeyFactory kf, EncodedKeySpec spec) { try { return kf.generatePrivate(spec); } catch(InvalidKeySpecException e) { throw new JwtInitializationException(e); } } }
核心问题
- 是否需要将该Secret的YAML文件加入Spring项目?若需要,原因是什么?
- 如何直接从OpenShift的目录中读取该Secret的数据?
更新补充
我知道可以通过以下Pod配置将私钥设为环境变量,但不清楚Spring中后续如何配置读取该密钥,是否需要指定YAML文件的URL?需要具体实现指导:
apiVersion: v1 kind: Pod metadata: name: secret-example-pod spec: containers: - name: secret-test-container image: busybox command: [ "/bin/sh", "-c", "export" ] env: - name: PRIVATE_KEY valueFrom: secretKeyRef: name: jwt-signing-keys key: private-key
解答
问题1:是否需要将Secret的YAML文件加入Spring项目?
不需要,原因如下:
- Secret是OpenShift集群的运维层面资源,不属于应用代码范畴,将其加入项目会导致敏感密钥暴露在代码仓库中,违反安全规范。
- 应用运行时直接从OpenShift环境获取Secret值即可,无需将静态YAML文件打包进应用镜像或代码库。
问题2:读取OpenShift Secret的两种实现方式
方式一:通过环境变量读取(推荐)
这是符合12-Factor应用规范的常用方式,步骤如下:
在Deployment中配置环境变量
实际部署建议用Deployment替代单个Pod,配置示例:apiVersion: apps/v1 kind: Deployment metadata: name: your-spring-app namespace: test-dev spec: replicas: 1 selector: matchLabels: app: your-spring-app template: metadata: labels: app: your-spring-app spec: containers: - name: your-spring-container image: your-spring-app-image:latest env: - name: JWT_PRIVATE_KEY valueFrom: secretKeyRef: name: jwt-signing-keys key: private-keySpring中读取环境变量
有两种实现方式:- 直接注入环境变量
修改JwtKeyProvider类,移除ResourceUtil依赖,直接读取环境变量:@Component @RequiredArgsConstructor public class JwtKeyProvider { private final Base64Util base64Util; @Value("${JWT_PRIVATE_KEY}") private String privateKeyRaw; @Getter private PrivateKey privateKey; @PostConstruct public void init() { privateKey = readKey( privateKeyRaw, "PRIVATE", this::privateKeySpec, this::privateKeyGenerator ); } private <T extends Key> T readKey(String keyString, String headerSpec, Function<String, EncodedKeySpec> keySpec, BiFunction<KeyFactory, EncodedKeySpec, T> keyGenerator) { try { // 若Secret中存储的是带PEM头的私钥,保留头尾替换逻辑;若为纯Base64编码的PKCS8内容,可跳过此步骤 keyString = keyString .replace("-----BEGIN " + headerSpec + " KEY-----", "") .replace("-----END " + headerSpec + " KEY-----", "") .replaceAll("\\s+", ""); return keyGenerator.apply(KeyFactory.getInstance("RSA"), keySpec.apply(keyString)); } catch(NoSuchAlgorithmException e) { throw new JwtInitializationException(e); } } private EncodedKeySpec privateKeySpec(String data) { return new PKCS8EncodedKeySpec(base64Util.decode(data)); } private PrivateKey privateKeyGenerator(KeyFactory kf, EncodedKeySpec spec) { try { return kf.generatePrivate(spec); } catch(InvalidKeySpecException e) { throw new JwtInitializationException(e); } } } - 通过配置文件映射
在application.properties中添加映射:
再在jwt.private-key=${JWT_PRIVATE_KEY}JwtKeyProvider中注入:
后续处理逻辑与直接注入方式一致。@Value("${jwt.private-key}") private String privateKeyRaw;
- 直接注入环境变量
方式二:通过挂载Secret到容器目录读取
OpenShift支持将Secret挂载为容器内的文件,步骤如下:
在Deployment中配置Volume挂载
apiVersion: apps/v1 kind: Deployment metadata: name: your-spring-app namespace: test-dev spec: replicas: 1 selector: matchLabels: app: your-spring-app template: metadata: labels: app: your-spring-app spec: volumes: - name: jwt-keys-volume secret: secretName: jwt-signing-keys containers: - name: your-spring-container image: your-spring-app-image:latest volumeMounts: - name: jwt-keys-volume mountPath: /opt/app/keys readOnly: true挂载完成后,容器内
/opt/app/keys/private-key文件会包含Secret中private-key解码后的原始内容。Spring中读取挂载文件
修改JwtKeyProvider的init方法,直接读取挂载路径:@PostConstruct public void init() { privateKey = readKey( "file:/opt/app/keys/private-key", "PRIVATE", this::privateKeySpec, this::privateKeyGenerator ); }原有
readKey方法无需大幅修改,ResourceUtil可直接处理file:前缀的资源路径。
关键注意事项
- Secret的
data字段值是Base64编码的,OpenShift在传递环境变量或挂载文件时会自动解码,Spring中无需重复解码(除非存储时做了双重编码)。 - 绝对不要将Secret的YAML文件提交到代码仓库,避免敏感信息泄露。
- 优先使用环境变量方式,更简洁且便于多环境适配。
内容的提问来源于stack exchange,提问作者DiegoMG
相关产品推荐
相关产品推荐

