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

SFCC平台调用Google API时JWT签名无效问题求助

SFCC(Demandware)平台获取Google API访问令牌签名问题排查

问题现象

  • 保留Google服务账号私钥首尾的-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----标识时,调用signature.sign抛出密钥解析错误:

Wrapped com.demandware.beehive.core.internal.crypt.SignatureException: com.demandware.beehive.core.internal.crypt.SignatureException: com.demandware.beehive.core.internal.crypt.EncryptionException: Error creating private key from string (length: 1677), algorithm = 'RSA': java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=111, too big

  • 移除首尾标识后能生成JWT,但请求Google API返回签名无效:

{"error":"invalid_grant","error_description":"Invalid JWT Signature."}

  • 调整签名的Base64替换规则(仅替换/为_并移除末尾=),仍返回签名错误:

{"error": "invalid_grant","error_description": "java.security.SignatureException: Invalid signature for token: ...}

现有代码

var Signature = require("dw/crypto/Signature");  
var Encoding = require('dw/crypto/Encoding'); 
var Bytes = require('dw/util/Bytes');

function execute( args : PipelineDictionary ) : Number
{
    var service = GoogleAPIHttpFormService.getService();
    var jwt = createJWT();
    service.addHeader("Content-Type", "application/x-www-form-urlencoded");
    service.setRequestMethod("POST");
    service.addParam('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
    service.addParam('assertion', jwt);
    
    var result = service.call();
    
    return PIPELET_NEXT;
}

function createJWT() {
    var json = {
          "private_key": "...private key code...",
      "client_email": "*****@*******.iam.gserviceaccount.com"
    };
    
    var date = new Date();
    var iat = (date.getTime() / 1000);
    var exp = iat + 3600; //the current time + 1 hour in seconds
    
    var jwtHeader = {"alg":"RS256","typ":"JWT"};
    var jwtClaimSet = {
            "iss": json.client_email,
        "scope": "https://www.googleapis.com/auth/analytics.readonly",
        "aud": "https://oauth2.googleapis.com/token",
        "exp": exp,
        "iat": iat
    };
    
    var signature = new Signature();
    //Encode the Header as Base64  
    let headerBase64 = Encoding.toBase64(new Bytes(JSON.stringify(jwtHeader)));  

    //Encode the Payload as Base64  
    let jwtClaimSetBase64 = Encoding.toBase64(new Bytes(JSON.stringify(jwtClaimSet)));  

    //Create the content of the JWT Signature  
    var signatureStr = headerBase64 + "." + jwtClaimSetBase64;  

    var token = signature.sign(signatureStr, json.private_key, 'SHA256withRSA');
 
    var signatureUrlEncoded = token.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/m, ''); 
    
    let jwt = headerBase64 + "." + jwtClaimSetBase64 + "." + signatureUrlEncoded;  

    return jwt;  
}

解决思路

1. 私钥处理规范

移除首尾标识后,需保留私钥原始的换行结构(每64字符换行),不要将私钥压缩为单行字符串。SFCC的Signature类要求RSA私钥为PKCS#8格式的PEM内容,换行缺失会导致密钥解析失败。

2. 全链路Base64URL编码修正

JWT的头部、Payload、签名三部分都需要使用Base64URL编码,而非标准Base64。当前代码仅处理了签名部分,需补充头部和Payload的编码转换:

// 头部Base64URL编码
let headerBase64 = Encoding.toBase64(new Bytes(JSON.stringify(jwtHeader)))
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

// Payload Base64URL编码
let jwtClaimSetBase64 = Encoding.toBase64(new Bytes(JSON.stringify(jwtClaimSet)))
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

3. 签名内容的字节编码一致性

调用signature.sign时,确保签名内容以UTF-8编码传入,避免平台默认编码导致的字节不一致:

var signatureBytes = new Bytes(signatureStr, "UTF-8");
var token = signature.sign(signatureBytes, json.private_key, 'SHA256withRSA');

4. 时间戳精度修正

Google对JWT时间戳要求整数秒,需对时间戳取整避免小数:

var iat = Math.floor(date.getTime() / 1000);
var exp = iat + 3600;

5. 签名转换规则简化

签名的Base64URL转换无需多行修饰符,使用简单正则即可:

var signatureUrlEncoded = token.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

内容的提问来源于stack exchange,提问作者Angel Ivanov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 13:13:20