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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:57:16