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请求等,最终完成用户在安卓应用内支付购买信息的校验。

GoolePlay 充值 OAuth 2.0 后端验证_第1张图片

通过原文和图解我们可以知道这样一个流程(下文会详细说明):

        一. 在Google Developer Console中创建一个 Web Application账户,得到client_id,client_secret 和 redirect_uri,这3个参数后边步骤常用到(此为前提)

        二. 获取Authorization code 

        三. 利用code 获取access_token,refresh_token

        四. 进一步可利用refresh_token获取新的access_token

        五. 使用access_token 调用Google API 达到最终目的(如果access_token过时,回到第四步)

        需注意的是:在第三步操作,当我们第一次利用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后,就是一个转折点。  

        下面详细分解步骤:

一、在Google Developer Console中创建一个Web application账户

    (这里使用的是新版的Google Developer Console页面,其实可在Account settings中设置为中文显示~)

GoolePlay 充值 OAuth 2.0 后端验证_第2张图片

        

        其中4.可以随意填写。创建完成后可以看下下图所示:

GoolePlay 充值 OAuth 2.0 后端验证_第3张图片


        在这里我们拿到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;
	}

你可能感兴趣的:(游戏,OAuth,应用内支付,purchase,in-app,googleplay)