GoolePlay 充值 OAuth 2.0 後端驗證

       前段時間遊戲急於在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_idclient_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;
	}
相關文章
相關標籤/搜索