ColdFusion 2018使用服务账号实现Google OAuth2时请求报错求助
我用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

