前段時間遊戲急於在GoolePlay上線,明知道若是不加Auth2.0的校驗是不安全的仍是暫時略過了這一步,果真沒幾天就發現後臺記錄與玩家實際付費不太一致,懷疑有玩家盜刷遊戲元寶等,而且真實的走過了GooglePlay的全部支付流程完成道具兌換,時間一長嚴重性可想而知。通過查閱大量google官方文檔後把代碼補上,並在這裏記錄下OAuth 2.0 的使用,Google提供了OAuth2.0的好幾種使用用途,每種使用方法都有些不一樣,具體能夠看下 這篇博客。在這裏只寫OAuth 2.0 for Web Server Applications的使用,涉及refresh_token, access_token等的獲取和使用,以及如何向google發送GET和POST請求等,最終完成用戶在安卓應用內支付購買信息的校驗。java
經過原文和圖解咱們能夠知道這樣一個流程(下文會詳細說明):android
一. 在Google Developer Console中建立一個 Web Application帳戶,獲得client_id,client_secret 和 redirect_uri,這3個參數後邊步驟經常使用到(此爲前提)web
二. 獲取Authorization code api
三. 利用code 獲取access_token,refresh_token瀏覽器
四. 進一步可利用refresh_token獲取新的access_token安全
五. 使用access_token 調用Google API 達到最終目的(若是access_token過期,回到第四步)app
需注意的是:在第三步操做,當咱們第一次利用code獲取access_token時,谷歌會同時返回給你一個refresh_token,之後再次用code獲取access_token操做將不會再看到refresh_token,因此必定要保存下來。這個refresh_token是長期有效的,若是沒有明確的被應用管理者撤銷是不會過時的,而access_token則只有3600秒的時效,即1個小時,那麼問題來了,access_token和refresh_token是什麼關係?很明顯的,咱們最終是要使用access_token 去調用Google API,而access_token又有時效限制,因此當access_token過時後,咱們能夠用長效的refresh_token去再次獲取access_token,而且能夠能夠在任什麼時候間屢次獲取,沒有次數限制。其實當咱們獲得refresh_token後,就是一個轉折點。 ui
下面詳細分解步驟:google
1、在Google Developer Console中建立一個Web application帳戶
url
(這裏使用的是新版的Google Developer Console頁面,其實可在Account settings中設置爲中文顯示~)
其中4.能夠隨意填寫。建立完成後能夠看下下圖所示:
在這裏咱們拿到3個關鍵參數: client_id,client_secret,redirect_uris,,於下邊步驟。
可能會有人有疑問,怎麼就能肯定在google developer console 創建的project就於Googleplay上線的安卓應用有關聯呢?爲何能夠用這些參數得來的access_token去調用谷歌API?其實在Googleplay發佈應用時就有關聯project的操做,以後建立project的人能夠給其餘谷歌帳戶受權,這樣其餘谷歌帳戶能夠在本身的developer console頁面直接看到該project和如下的web application等, 而且可在下一步操做中登陸本身的谷歌帳戶獲取code。
二. 獲取Authorization code
https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher &response_type=code&access_type=offline&redirect_uri={REDIRECT_URIS}&client_id={CLIENT_ID}
咱們須要將這個URL以瀏覽器的形式打開,這時會跳出提示你Sign in with your Google Account,而後在用有project受權的谷歌帳戶登陸,地址欄會出現咱們所需的code。例如:https://www.example.com/oauth2callback?code=4/CpVOd8CljO_gxTRE1M5jtwEFwf8gRD44vrmKNDi4GSS.kr-GHuseD-oZEnp6UADFXm0E0MD3FlAI
三. 利用code 獲取access_token,refresh_token
https://accounts.google.com/o/oauth2/token? code={CODE}&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&redirect_uri={REDIRECT}&grant_type=authorization_code
咱們這一步的目的是獲取refresh_token,只要有了這個長效token,access_token是隨時能夠獲取的,第一次發起請求獲得的JSON字符串以下所示,之後再請求將再也不出現refresh_token,要保存好。expires_in是指access_token的時效,爲3600秒。
{"access_token": "ya29.3gC2jw5vm77YPkylq0H5sPJeJJDHX93Kq8qZHRJaMlknwJ85595eMogL300XKDOEI7zIsdeFEPY6zg", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "1/FbQD448CdDPfDEDpCy4gj_m3WDr_M0U5WupquXL_o"}
四. 進一步可利用refresh_token獲取新的access_token
https://accounts.google.com/o/oauth2/token? grant_type=refresh_token &client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&refresh_token={REFRESH_TOKEN}
這裏咱們要向谷歌發起POST請求,JAVA代碼以下:
/** 獲取access_token **/ private static Map<String,String> getAccessToken(){ final String CLIENT_ID = "填入你的client_id"; final String CLIENT_SECRET = "填入你的client_secret"; final String REFRESH_TOKEN = "填入上一步獲取的refresh_token"; Map<String,String> map = null; try { /** * https://accounts.google.com/o/oauth2/token?refresh_token={REFRESH_TOKEN} * &client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&grant_type=refresh_token */ URL urlGetToken = new URL("https://accounts.google.com/o/oauth2/token"); HttpURLConnection connectionGetToken = (HttpURLConnection) urlGetToken.openConnection(); connectionGetToken.setRequestMethod("POST"); connectionGetToken.setDoOutput(true); // 開始傳送參數 OutputStreamWriter writer = new OutputStreamWriter(connectionGetToken.getOutputStream()); writer.write("refresh_token="+REFRESH_TOKEN+"&"); writer.write("client_id="+CLIENT_ID+"&"); writer.write("client_secret="+CLIENT_SECRET+"&"); writer.write("grant_type=refresh_token"); writer.close(); //若響應碼爲200則表示請求成功 if(connectionGetToken.getResponseCode() == HttpURLConnection.HTTP_OK){ StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader( new InputStreamReader(connectionGetToken.getInputStream(), "utf-8")); String strLine = ""; while((strLine = reader.readLine()) != null){ sb.append(strLine); } // 取得谷歌回傳的信息(JSON格式) JSONObject jo = JSONObject.fromObject(sb.toString()); String ACCESS_TOKEN = jo.getString("access_token"); Integer EXPIRES_IN = jo.getInt("expires_in"); map = new HashMap<String,String>(); map.put("access_token", ACCESS_TOKEN); map.put("expires_in", String.valueOf(EXPIRES_IN)); // 帶入access_token的建立時間,用於以後判斷是否失效 map.put("create_time",String.valueOf((new Date().getTime()) / 1000)); logger.info("包含access_token的JSON信息爲: "+jo); } } catch (MalformedURLException e) { logger.error("獲取access_token失敗,緣由是:"+e); e.printStackTrace(); } catch (IOException e) { logger.error("獲取access_token失敗,緣由是:"+e); e.printStackTrace(); } return map; }
五. 使用access_token 調用Google API 達到最終目的(若是access_token過期,回到第四步)
在這裏我所須要獲取的是我在應用內給GooglePlay支付的購買信息,此類信息包含如下幾個屬性:(可參考Google Play Developer API下的Purchases.products)
A ProductPurchase resource indicates the status of a user's inapp product purchase.
{ "kind": "androidpublisher#productPurchase", "purchaseTimeMillis": long, "purchaseState": integer, (purchased:0 cancelled:1,咱們就是依靠這個判斷購買信息) "consumptionState": integer, "developerPayload": string}
帶着access_token參數向GoogleApi發起GET請求,Java代碼以下:
private static Map<String,String> cacheToken = null;//設置靜態變量,用於判斷access_token是否過時 public static GooglePlayBuyEntity getInfoFromGooglePlayServer(String packageName,String productId, String purchaseToken) { if(null != cacheToken){ Long expires_in = Long.valueOf(cacheToken.get("expires_in")); // 有效時長 Long create_time = Long.valueOf(cacheToken.get("create_time")); // access_token的建立時間 Long now_time = (new Date().getTime()) / 1000; if(now_time > (create_time + expires_in - 300)){ // 提早五分鐘從新獲取access_token cacheToken = getAccessToken(); } }else{ cacheToken = getAccessToken(); } String access_token = cacheToken.get("access_token"); GooglePlayBuyEntity buyEntity = null; try { /**這是寫這篇博客時間時的最新API,v2版本。 * https://www.googleapis.com/androidpublisher/v2/applications/{packageName} * /purchases/products/{productId}/tokens/{purchaseToken}?access_token={access_token} */ String url = "https://www.googleapis.com/androidpublisher/v2/applications"; StringBuffer getURL = new StringBuffer(); getURL.append(url); getURL.append("/" + packageName); getURL.append("/purchases/products"); getURL.append("/" + productId ); getURL.append("/tokens/" + purchaseToken); getURL.append("?access_token=" + access_token); URL urlObtainOrder = new URL(getURL.toString()); HttpURLConnection connectionObtainOrder = (HttpURLConnection) urlObtainOrder.openConnection(); connectionObtainOrder.setRequestMethod("GET"); connectionObtainOrder.setDoOutput(true); // 若是認證成功 if (connectionObtainOrder.getResponseCode() == HttpURLConnection.HTTP_OK) { StringBuilder sbLines = new StringBuilder(""); BufferedReader reader = new BufferedReader(new InputStreamReader( connectionObtainOrder.getInputStream(), "utf-8")); String strLine = ""; while ((strLine = reader.readLine()) != null) { sbLines.append(strLine); } // 把上面取回來的資料,放進JSONObject中,以方便我們直接存取到想要的參數 JSONObject jo = JSONObject.fromObject(sbLines.toString()); Integer status = jo.getInt("purchaseState"); if(status == 0){ //驗證成功 buyEntity = new GooglePlayBuyEntity(); buyEntity.setConsumptionState(jo.getInt("consumptionState")); buyEntity.setDeveloperPayload(jo.getString("developerPayload")); buyEntity.setKind(jo.getString("kind")); buyEntity.setPurchaseState(status); buyEntity.setPurchaseTimeMillis(jo.getLong("purchaseTimeMillis")); }else{ // 購買無效 buyEntity = new GooglePlayBuyEntity(); buyEntity.setPurchaseState(status); logger.info("從GooglePlay帳單校驗失敗,緣由是purchaseStatus爲" + status); } } } catch (Exception e) { e.printStackTrace(); buyEntity = new GooglePlayBuyEntity(); buyEntity.setPurchaseState(-1); } return buyEntity; }