在 Coldfusion 中为 google 服务帐户创建 JWT
Creating JWT in Coldfusion for google Service account
我对 JWT 的签名方面感到困惑。我相信我的 header 和声明集是正确的,因为我克服了最初写这篇文章时看到的任何错误。我的问题主要围绕签名。带有 HMACSHA256 的 HMac 是否正确?我想我可能对从哪里获得加密私钥感到困惑。如果有人有一些指导,那就太好了。
<cfset JWT_header = structNew()>
<cfset JWT_header['alg'] = 'RS256'>
<cfset JWT_header['typ'] = 'JWT'>
<cfset JWT_header = serializeJSON(JWT_header)>
<cfset JWT_claim_set = structNew()>
<cfset JWT_claim_set['iss'] = 'secret_iss'>
<cfset JWT_claim_set['scope'] = 'my_scope'>
<cfset JWT_claim_set['aud'] = 'https://www.googleapis.com/oauth2/v4/token'>
<cfset JWT_claim_set['exp'] = 'Time_Stamp')>
<cfset JWT_claim_set['iat'] = 'Time_Stamp')>
<cfset JWT_claim_set = serializeJSON(JWT_claim_set)>
<cfset data = ToBase64(JWT_header) & '.' & ToBase64(JWT_claim_set)>
<cfset hashedData = HMac(data, 'my_secret_private_key','HMACSHA256')>
<cfset signature = toBase64(hashedData)>
<cfset JWT = data & '.' & signature>
<cfhttp url="https://www.googleapis.com/oauth2/v4/token" method="post" result="result">
<cfhttpparam name="grant_type" type="formField" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
<cfhttpparam name="assertion" type="formField" value="#JWT#" />
</cfhttp>
<cfoutput>#result.filecontent#</cfoutput>
这个returns:
'{ "error": "invalid_grant", "error_description": "Invalid JWT Signature." }'
我使用 Ben Nadel 的代码 (https://www.bennadel.com/blog/2941-experimenting-with-rsa-encrypted-signature-generation-and-verification-in-coldfusion.htm) 得到它,但我必须修改它才能工作。我注释掉了与 public 键有关的任何内容,因为我没有使用它与 google 进行交互。如果我要增强它,我可以创建逻辑来寻找 public 或私钥的使用。接下来我跳过了 Pem 文件格式的任何内容,因为 google 没有使用它。现在可以了。
对于将来需要一些代码以使他们走上正确道路的任何人。此代码用于推送通知的 firebase 消息传递 api,但可适用于其他 google 服务。
<cfscript>
variables.service_json = deserializeJSON(fileRead(expandPath('./serviceaccountprivatekey.json')));
variables.timestamp = dateDiff("s", CreateDate(1970,1,1), now());
variables.timestampUTC = timestamp + 21600; //add 6 hour 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/firebase.messaging',
'aud': 'https://www.googleapis.com/oauth2/v4/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://www.googleapis.com/oauth2/v4/token"
method="POST"
result="res"
>
<cfhttpparam name="grant_type" type="formField" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
<cfhttpparam name="assertion" type="formField" value="#variables.jwt_signed#" />
</cfhttp>
<cfset variables.res = deserializeJSON(res.filecontent) />
<cfscript>
variables.body = {
"message": {
"notification": {
"title": "test",
"body": "test test test"
},
"token": "e7blahblahSQ:thisisanexamplefirebasemessengingtokenpleaseputyourownonehere"
}
};
</cfscript>
<cfhttp url="https://fcm.googleapis.com/v1/projects/{project_id}/messages:send" method="post" result="res">
<cfhttpparam type="header" name="Content-type" value="application/json" />
<cfhttpparam type="header" name="Authorization" value="Bearer #variables.res.access_token#" />
<cfhttpparam type="body" value="#serializeJSON(body)#" />
</cfhttp>
<cfdump var="#res.fileContent#">
我对 JWT 的签名方面感到困惑。我相信我的 header 和声明集是正确的,因为我克服了最初写这篇文章时看到的任何错误。我的问题主要围绕签名。带有 HMACSHA256 的 HMac 是否正确?我想我可能对从哪里获得加密私钥感到困惑。如果有人有一些指导,那就太好了。
<cfset JWT_header = structNew()>
<cfset JWT_header['alg'] = 'RS256'>
<cfset JWT_header['typ'] = 'JWT'>
<cfset JWT_header = serializeJSON(JWT_header)>
<cfset JWT_claim_set = structNew()>
<cfset JWT_claim_set['iss'] = 'secret_iss'>
<cfset JWT_claim_set['scope'] = 'my_scope'>
<cfset JWT_claim_set['aud'] = 'https://www.googleapis.com/oauth2/v4/token'>
<cfset JWT_claim_set['exp'] = 'Time_Stamp')>
<cfset JWT_claim_set['iat'] = 'Time_Stamp')>
<cfset JWT_claim_set = serializeJSON(JWT_claim_set)>
<cfset data = ToBase64(JWT_header) & '.' & ToBase64(JWT_claim_set)>
<cfset hashedData = HMac(data, 'my_secret_private_key','HMACSHA256')>
<cfset signature = toBase64(hashedData)>
<cfset JWT = data & '.' & signature>
<cfhttp url="https://www.googleapis.com/oauth2/v4/token" method="post" result="result">
<cfhttpparam name="grant_type" type="formField" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
<cfhttpparam name="assertion" type="formField" value="#JWT#" />
</cfhttp>
<cfoutput>#result.filecontent#</cfoutput>
这个returns:
'{ "error": "invalid_grant", "error_description": "Invalid JWT Signature." }'
我使用 Ben Nadel 的代码 (https://www.bennadel.com/blog/2941-experimenting-with-rsa-encrypted-signature-generation-and-verification-in-coldfusion.htm) 得到它,但我必须修改它才能工作。我注释掉了与 public 键有关的任何内容,因为我没有使用它与 google 进行交互。如果我要增强它,我可以创建逻辑来寻找 public 或私钥的使用。接下来我跳过了 Pem 文件格式的任何内容,因为 google 没有使用它。现在可以了。
对于将来需要一些代码以使他们走上正确道路的任何人。此代码用于推送通知的 firebase 消息传递 api,但可适用于其他 google 服务。
<cfscript>
variables.service_json = deserializeJSON(fileRead(expandPath('./serviceaccountprivatekey.json')));
variables.timestamp = dateDiff("s", CreateDate(1970,1,1), now());
variables.timestampUTC = timestamp + 21600; //add 6 hour 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/firebase.messaging',
'aud': 'https://www.googleapis.com/oauth2/v4/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://www.googleapis.com/oauth2/v4/token"
method="POST"
result="res"
>
<cfhttpparam name="grant_type" type="formField" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
<cfhttpparam name="assertion" type="formField" value="#variables.jwt_signed#" />
</cfhttp>
<cfset variables.res = deserializeJSON(res.filecontent) />
<cfscript>
variables.body = {
"message": {
"notification": {
"title": "test",
"body": "test test test"
},
"token": "e7blahblahSQ:thisisanexamplefirebasemessengingtokenpleaseputyourownonehere"
}
};
</cfscript>
<cfhttp url="https://fcm.googleapis.com/v1/projects/{project_id}/messages:send" method="post" result="res">
<cfhttpparam type="header" name="Content-type" value="application/json" />
<cfhttpparam type="header" name="Authorization" value="Bearer #variables.res.access_token#" />
<cfhttpparam type="body" value="#serializeJSON(body)#" />
</cfhttp>
<cfdump var="#res.fileContent#">