在Java项目中对接Google相关服务时,使用服务账户进行身份认证是主流方案,而JWT签名是服务账户认证的核心环节,其中私钥的安全读取直接决定了整个认证流程的安全性。如果私钥被硬编码在代码里或者明文存储在配置文件中,很容易在代码泄露、配置暴露的场景下引发安全风险。

私钥的安全存储规范
首先需要将Google服务账户生成的私钥文件妥善存储,不建议直接将私钥内容写入代码或者普通配置文件,推荐的安全存储方式有几种:
- 将私钥文件放在服务器的非Web可访问目录,通过文件路径读取
- 将私钥内容加密后存储在配置中心,运行时解密后使用
- 使用环境变量存储私钥的编码内容,避免明文落盘
Java读取私钥的实现步骤
1. 准备私钥文件
从Google Cloud控制台创建服务账户后,会下载到一个JSON格式的密钥文件,其中包含private_key字段,我们需要提取该字段的内容用于后续操作。如果是PEM格式的私钥文件,也可以直接读取文件内容。
2. 加载私钥工具方法
下面是通过文件路径读取私钥的实现代码,该方法会将私钥内容转换为Java可用的PrivateKey对象:
import java.io.BufferedReader;
import java.io.FileReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class PrivateKeyReader {
/**
* 从PEM格式私钥文件读取私钥
* @param filePath 私钥文件路径
* @return 私钥对象
* @throws Exception 读取或解析异常
*/
public static PrivateKey loadPrivateKey(String filePath) throws Exception {
// 读取文件内容并去除PEM格式的头尾标识和换行符
StringBuilder keyContent = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("-----BEGIN PRIVATE KEY-----") || line.contains("-----END PRIVATE KEY-----")) {
continue;
}
keyContent.append(line);
}
}
// Base64解码私钥内容
byte[] keyBytes = Base64.getDecoder().decode(keyContent.toString());
// 生成PKCS8格式的私钥规范
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
// 使用RSA算法生成私钥工厂并生成私钥
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
}
3. 从环境变量读取私钥的场景
如果私钥内容存储在环境变量中,可以使用以下方法解析:
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class EnvPrivateKeyReader {
/**
* 从环境变量读取私钥内容并转换为私钥对象
* @param envKey 存储私钥的环境变量名
* @return 私钥对象
* @throws Exception 解析异常
*/
public static PrivateKey loadPrivateKeyFromEnv(String envKey) throws Exception {
String privateKeyContent = System.getenv(envKey);
if (privateKeyContent == null || privateKeyContent.isEmpty()) {
throw new IllegalArgumentException("环境变量" + envKey + "不存在或为空");
}
// 去除可能的换行符和空格
privateKeyContent = privateKeyContent.replaceAll("\s+", "");
byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
}
使用私钥进行JWT签名
读取到私钥之后,就可以使用它进行JWT签名,这里使用JJWT库作为示例,首先需要在项目中引入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
下面是完整的JWT签名示例代码:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.PrivateKey;
import java.util.Date;
public class JwtSigner {
/**
* 使用私钥生成Google服务账户所需的JWT令牌
* @param privateKey 读取到的私钥对象
* @param serviceAccountEmail 服务账户邮箱
* @param audience JWT的受众,通常是Google服务的接口地址
* @return 签名后的JWT字符串
*/
public static String generateSignedJwt(PrivateKey privateKey, String serviceAccountEmail, String audience) {
long now = System.currentTimeMillis();
// JWT有效期设置为1小时
Date expiryDate = new Date(now + 3600 * 1000);
return Jwts.builder()
// 设置JWT头部,算法为RS256
.setHeaderParam("alg", "RS256")
.setHeaderParam("typ", "JWT")
// 设置JWT载荷
.setIssuer(serviceAccountEmail) // 签发者为服务账户邮箱
.setSubject(serviceAccountEmail) // 主题设置为服务账户邮箱
.setAudience(audience) // 受众为Google服务接口地址
.setIssuedAt(new Date(now)) // 签发时间
.setExpiration(expiryDate) // 过期时间
// 使用私钥和RS256算法进行签名
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
}
安全注意事项
- 不要将私钥文件提交到代码仓库,建议在.gitignore中添加私钥文件的过滤规则
- 生产环境中避免打印私钥相关的日志,防止日志泄露敏感信息
- 定期轮换服务账户私钥,降低私钥泄露后的影响范围
- 读取私钥的代码需要做好异常处理,避免异常信息中暴露私钥路径或内容
使用示例
将上面的方法组合起来,完整的调用流程如下:
public class Main {
public static void main(String[] args) {
try {
// 方式1:从文件路径读取私钥
PrivateKey privateKey = PrivateKeyReader.loadPrivateKey("/secure/path/service-account.pem");
// 方式2:从环境变量读取私钥(二选一即可)
// PrivateKey privateKey = EnvPrivateKeyReader.loadPrivateKeyFromEnv("GOOGLE_SERVICE_PRIVATE_KEY");
String serviceAccountEmail = "test@your-project.iam.gserviceaccount.com";
// Google OAuth2令牌接口的受众地址
String audience = "https://oauth2.googleapis.com/token";
String jwt = JwtSigner.generateSignedJwt(privateKey, serviceAccountEmail, audience);
System.out.println("生成的JWT令牌:" + jwt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
JavaJWT签名Google服务账户私钥读取修改时间:2026-07-03 19:18:35