經過上一篇介紹如何進行 Google Play 驗證 In-app Billing 如何進行設定後,本篇將解說驗證的工作流程。
Google API 使用 OAuth 2.0 認證機制,在認證之前我們需要先產生 JWT 向 Google 要求一組 Token,
然後再將 Google 給的 Token 在 Call API 時一并放在 Request 內一起送過去。
什麼是 JWT呢?簡單來說它是一個開放標準基於 json 的一種認證方式可以在不同的網域間共享資訊。
想更進一步的了解 JWT可以到 http://self-issued.info/docs/draft-ietf-oauth-jwt-bearer.html
驗證的第一步就是先向 Google 要來一組 Token,往後的 API 呼叫都需要用到它,
附帶一提 Token 有時效性(印象中最高可設定1小時)
Google API 使用 OAuth 2.0 認證機制,在認證之前我們需要先產生 JWT 向 Google 要求一組 Token,
然後再將 Google 給的 Token 在 Call API 時一并放在 Request 內一起送過去。
什麼是 JWT呢?簡單來說它是一個開放標準基於 json 的一種認證方式可以在不同的網域間共享資訊。
想更進一步的了解 JWT可以到 http://self-issued.info/docs/draft-ietf-oauth-jwt-bearer.html
驗證的第一步就是先向 Google 要來一組 Token,往後的 API 呼叫都需要用到它,
附帶一提 Token 有時效性(印象中最高可設定1小時)
///
/// 向 Google 索取存取API所需的 Token
///
public static void FetchGoogleAccessToken() {
//JOSE Header 設定使用何種加密方式進行簽章
var header = new { typ = "JWT", alg = "RS256" };
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var issueTime = DateTime.UtcNow;
var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
var exp = (int)issueTime.AddMinutes(60).Subtract(utc0).TotalSeconds;
//ClamsSet傳送到 Google 進行 Auth 時的資訊
var claimset = new {
iss = /*上一篇有提到的 EMAIL ADDRESS */,
scope = "https://www.googleapis.com/auth/androidpublisher",
aud = "https://accounts.google.com/o/oauth2/token",
iat,
exp
};
// Encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
//網路上找的到 Base64UrlEncode 不貼碼了
var headerEncoded = Base64UrlEncode(headerBytes);
// Encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
//網路上找的到 Base64UrlEncode 不貼碼了
var claimsetEncoded = Base64UrlEncode(claimsetBytes);
//C# 6.0 的語言 可替換成 string.Format("{0}.{1}",headerEncoded,claimsetEncoded)
var input = $"{headerEncoded}.{claimsetEncoded}";
var inputBytes = Encoding.UTF8.GetBytes(input);
// signiture
var certificate = new X509Certificate2(
Path.Combine(
/*存放P12檔的路徑*/,
/*P12檔的檔名*/),
"notasecret");
var rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
var cspParam = new CspParameters {
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = Base64UrlEncode(signatureBytes);
//JWT 需要將三個連在一起︰Header,Claims Set 和 Sign 然後用 . 隔開
var jwt = $"{headerEncoded}.{claimsetEncoded}.{signatureEncoded}";
var r = (HttpWebRequest)WebRequest.Create("https://accounts.google.com/o/oauth2/token");
r.Method = "POST";
r.ContentType = "application/x-www-form-urlencoded";
r.UserAgent = string.Format(Section.Get.Common.Culture, " Mozilla/4.0 (compatible; Win32; {0}.{1})", 1, 0);
ServicePointManager.ServerCertificateValidationCallback = (sender, certificates, chain, sslPolicyErrors) => true;
var postData = $@"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={jwt}";
var postBytes = Encoding.UTF8.GetBytes(postData);
r.ContentLength = postBytes.Length;
using (var postStream = r.GetRequestStream()) {
postStream.Write(postBytes, 0, postBytes.Length);
postStream.Close();
}
Task.Factory.FromAsync(r.BeginGetResponse, r.EndGetResponse, null).ContinueWith(t => {
try {
var result = t.Result.GetResponseStream();
if (null == result) return;
using (var sr = new StreamReader(result, Encoding.UTF8)) {
var token = sr.ReadToEnd();
//GoogleToken 這個類別只有三個欄位 string access_token、string token_type、int expires_in
var tokenObj = JsonConvert.DeserializeObject(token);
//tokenObj.access_token 需要保存下來
Log.Info($"NewAccessToken={tokenObj.access_token}");
sr.Close();
}
}
catch (Exception ex) {
Log.Error(ex.Message, ex);
}
}, TaskContinuationOptions.ExecuteSynchronously);
}
最後,當玩家玩成儲值流程時先別急著將道具或代幣給玩家,先向 Google 問一下這位玩家剛剛是否有真的消費
實作的方式可以參考下面的連結進行實作
https://developers.google.com/android-publisher/api-ref/purchases/products/get#request
實作的方式可以參考下面的連結進行實作
https://developers.google.com/android-publisher/api-ref/purchases/products/get#request