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

ColdFusion 2018使用服务账号实现Google OAuth2时请求报错求助

问题排查:ColdFusion生成JWT提交Google OAuth2返回Bad Request

我用ColdFusion代码生成了签名JWT,但通过cfhttp POST提交到Google OAuth2令牌端点时,返回错误:{ "error": "invalid_request", "error_description": "Bad Request" }。我替换了服务账号JSON里的真实凭证,但保留了所有属性和值的格式。代码参考了StackOverflow上的Firebase示例,现在需要帮忙排查问题。

以下是我的代码:

<cfscript>
    variables.service_json = deserializeJSON(
        '{
            "type": "service_account",
            "project_id": "my-project-123456",
            "private_key_id": "11111aa22222bb33333cc44444dd55555ee66666",
            "private_key": "-----BEGIN PRIVATE KEY-----\naaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000==\n-----END PRIVATE KEY-----\n",
            "client_email": "my-service-account-email@my-project-123456.iam.gserviceaccount.com",
            "client_id": "111222333444555666777",
            "auth_uri": "https://accounts.google.com/o/oauth2/auth",
            "token_uri": "https://oauth2.googleapis.com/token",
            "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
            "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-service-account-email%40my-project-123456.iam.gserviceaccount.com",
            "universe_domain": "googleapis.com"
        }'
    );
    
    variables.timestamp = dateDiff("s", CreateDate(1970,1,1), now());
    variables.timestampUTC = timestamp + 8*60*60; //add 8 hours to convert to utc 
    
    //generate jwt 
    variables.jwt_header = {
        'alg': 'RS256',
        'typ': 'JWT'
    };
    variables.jwt_header = serializeJSON(variables.jwt_header);
    variables.jwt_header = toBase64(variables.jwt_header);
    
    variables.jwt_claim = {
        'iss': service_json.client_email,
        'scope': 'https://www.googleapis.com/auth/analytics.readonly',
        'aud': 'https://oauth2.googleapis.com/token',
        'iat': timestampUTC,
        'exp': (timestampUTC + 3600)    
    };
    variables.jwt_claim = serializeJSON(variables.jwt_claim);
    variables.jwt_claim = toBase64(variables.jwt_claim);
    variables.jwt = variables.jwt_header & '.' & variables.jwt_claim;
    
    //sign jwt
    variables.keyText = reReplace( service_json.private_key, "-----(BEGIN|END)[^\r\n]+", "", "all" );
    variables.keyText = trim( keyText );
    
    variables.privateKeySpec = createObject( "java", "java.security.spec.PKCS8EncodedKeySpec" ).init(binaryDecode( variables.keyText, "base64" ));
    variables.privateKey = createObject( "java", "java.security.KeyFactory" ).getInstance( javaCast( "string", "RSA" ) ).generatePrivate( privateKeySpec );
    variables.signer = createObject( "java", "java.security.Signature" ).getInstance( javaCast( "string", 'SHA256withRSA' ));
    
    variables.signer.initSign( variables.privateKey );
    variables.signer.update( charsetDecode( variables.jwt, "utf-8" ) );
    variables.signedBytes = signer.sign();
    variables.signedBase64 = toBase64(signedBytes);
    
    variables.jwt_signed = variables.jwt & '.' & variables.signedBase64;
</cfscript>
    
<cfhttp url="https://oauth2.googleapis.com/token" method="POST" result="res">
    <cfhttpparam type="formfield" name="grant_type" value="urn:ietf:params:oauth:grant-type:jwt-bearer">
    <cfhttpparam type="formfield" name="assertion" value="#variables.jwt_signed#">
</cfhttp>

<cfdump var="#variables.jwt_signed#">
<cfdump var="#res#">

我必须使用服务账号方案,不能用OAuth2客户端ID。试过第三方的JSONWebTokens组件,但它需要公钥,而Google OAuth2并没有提供,所以目前最接近可行的就是上面的代码。


核心排查点
  • 时间戳转换逻辑错误:你通过本地时间戳加8小时转UTC的方式不可靠,now()返回服务器本地时间,不同时区会导致偏差。正确做法是直接获取UTC时间戳:

    variables.timestampUTC = dateDiff("s", CreateDate(1970,1,1), dateConvert("local2utc", now()));
    

    Google对JWT的iat(签发时间)和exp(过期时间)准确性要求极高,时间偏差过大直接会被拒绝。

  • Base64编码不符合JWT规范:JWT要求使用URL安全的Base64编码(替换+为-,/为_,移除末尾=),但ColdFusion默认toBase64()生成标准Base64,需手动处理:

    // 处理header
    variables.jwt_header = replace(replace(toBase64(variables.jwt_header), "+", "-", "all"), "/", "_", "all").replaceAll("=+$", "");
    // 处理claim
    variables.jwt_claim = replace(replace(toBase64(variables.jwt_claim), "+", "-", "all"), "/", "_", "all").replaceAll("=+$", "");
    // 处理签名部分
    variables.signedBase64 = replace(replace(toBase64(signedBytes), "+", "-", "all"), "/", "_", "all").replaceAll("=+$", "");
    
  • 变量作用域遗漏:签名代码中signedBytes = signer.sign();未加variables.前缀,在ColdFusion中可能成为全局变量导致值异常,需改为:

    variables.signedBytes = variables.signer.sign();
    
  • 私钥处理验证:确保私钥移除首尾标识后没有多余空白字符,可输出variables.keyText检查是否为连续的Base64字符串,避免二进制解码失败。


内容的提问来源于stack exchange,提问作者the sea in storm

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 14:37:03