Java使用JWK格式私钥生成Line API RS256 JWT报密钥格式错误
问题原因
报错核心是密钥格式与解析逻辑不匹配:
- 你持有的是JWK(JSON Web Key)格式RSA私钥,本质是JSON结构存储的RSA参数集合
- 代码中使用的
PKCS8EncodedKeySpec仅接受PKCS#8标准DER编码的二进制私钥内容,直接传入JWK的JSON字节、或对JSON做Base64解码都不符合格式要求,必然抛出无效密钥格式异常 - Python环境可正常运行是因为调用了专门解析JWK格式的
RSAAlgorithm.from_jwk()方法,你当前使用的Java JWT库默认没有内置JWK解析能力,无法直接读取JSON格式私钥。
解决方案
不需要把JWK转成PKCS8格式,直接解析JWK中Base64URL编码的RSA参数,构造RSAPrivateCrtKeySpec即可生成可用的RSA私钥对象,完整可运行代码如下:
import cn.hutool.core.io.resource.ResourceUtil; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.extern.slf4j.Slf4j; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.interfaces.RSAPrivateKey; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; @Slf4j public class LineJwtUtil { /** * JWK内Base64URL编码数值转BigInteger */ private static BigInteger base64UrlToBigInt(String base64UrlStr) { // 替换Base64URL特殊字符为标准Base64字符 String standardBase64 = base64UrlStr.replace('-', '+').replace('_', '/'); // 补全Base64所需的=填充 int padLen = (4 - (standardBase64.length() % 4)) % 4; StringBuilder sb = new StringBuilder(standardBase64); for (int i = 0; i < padLen; i++) { sb.append("="); } byte[] decodeBytes = Base64.getDecoder().decode(sb.toString()); return new BigInteger(1, decodeBytes); } public String generateLineAccessTokenJwt() { String token = null; try { // 读取JWK私钥文件 String jwkContent = ResourceUtil.readStr("line_key/privatekey-dev.json", StandardCharsets.UTF_8); ObjectMapper objectMapper = new ObjectMapper(); JsonNode jwkNode = objectMapper.readTree(jwkContent); // 解析JWK中所有RSA CRT参数 BigInteger n = base64UrlToBigInt(jwkNode.get("n").asText()); BigInteger e = base64UrlToBigInt(jwkNode.get("e").asText()); BigInteger d = base64UrlToBigInt(jwkNode.get("d").asText()); BigInteger p = base64UrlToBigInt(jwkNode.get("p").asText()); BigInteger q = base64UrlToBigInt(jwkNode.get("q").asText()); BigInteger dp = base64UrlToBigInt(jwkNode.get("dp").asText()); BigInteger dq = base64UrlToBigInt(jwkNode.get("dq").asText()); BigInteger qi = base64UrlToBigInt(jwkNode.get("qi").asText()); // 构造RSA私钥 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPrivateCrtKeySpec privateKeySpec = new RSAPrivateCrtKeySpec(n, e, d, p, q, dp, dq, qi); RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec); // 构造Payload Map<String, Object> payloadClaims = new HashMap<>(); payloadClaims.put("iss", "替换为你的LINE Channel ID"); payloadClaims.put("sub", "替换为你的LINE Channel ID"); payloadClaims.put("aud", "https://api.line.me/"); // *注意:Date构造器入参为毫秒级时间戳,原代码漏乘1000会导致JWT刚生成就过期* payloadClaims.put("exp", new Date(System.currentTimeMillis() + 30 * 60 * 1000)); payloadClaims.put("token_exp", 30 * 24 * 60 * 60); // 生成JWT token = Jwts.builder() .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "RS256") .setHeaderParam("kid", "替换为你的LINE Channel私钥对应的Kid") .setClaims(payloadClaims) .signWith(SignatureAlgorithm.RS256, privateKey) .compact(); } catch (Exception e) { log.error("生成LINE JWT失败", e); } return token; } }
简化方案(可选)
如果不想手动解析JWK参数,可以引入自带JWK解析能力的依赖,比如nimbus-jose-jwt,解析私钥仅需2行代码:
import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.RSAKey; import java.security.interfaces.RSAPrivateKey; // 直接解析JWK字符串为私钥 RSAKey rsaJwk = (RSAKey) JWK.parse(jwkContent); RSAPrivateKey privateKey = rsaJwk.toRSAPrivateKey();
注意事项
- JWK中所有数值参数为无填充Base64URL编码,不能直接用普通Base64解码器解码,必须先做字符替换和填充补全
- LINE接口要求JWT的
aud固定为https://api.line.me/,填错会直接返回鉴权失败 token_exp字段单位为秒,代表生成的Channel Access Token有效期,最长可设置为30天
内容的提问来源于stack exchange,提问作者Lobs
相关产品推荐
相关产品推荐

