SFCC平台调用Google API时JWT签名无效问题求助
问题现象
- 保留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

