You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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);
        }
    }
}

核心问题

  1. 是否需要将该Secret的YAML文件加入Spring项目?若需要,原因是什么?
  2. 如何直接从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应用规范的常用方式,步骤如下:

  1. 在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-key
    
  2. Spring中读取环境变量
    有两种实现方式:

    • 直接注入环境变量
      修改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挂载为容器内的文件,步骤如下:

  1. 在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解码后的原始内容。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 22:24:12