Using OAuth 2.0 for Web Server Applications, verifying a user's Android in-app subscription

在写本文之前先说些题外话。

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

先贴下关于Using OAuth 2.0 for Web Server Applications的解释的谷歌官方原文

The authorization sequence begins when your application redirects a browser to a Google URL; the URL includes query parameters that indicate the type of access being requested. As in other scenarios, Google handles
user authentication, session selection, and user consent. The result is an authorization code, which Google returns to your application in a query string.

After receiving the authorization code, your application can exchange the code (along with a client ID and client secret) for an access token and, in some cases, a refresh token.

The application can then use the access token to access a Google API.

If a refresh token is present in the authorization code exchange, then it can be used to obtain new access tokens at any time. This is called offline access, because the user does not have to be present at the browser
when the application obtains a new access token.

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

一. 在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中设置为中文显示~)

其中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;
	}

到这里就写完了,如果有什么疑问可以留言。过两天我会再写篇ios应用内支付,苹果商店AppStore购买信息校验的博客,跟此篇是同一个性质的问题。

Using OAuth 2.0 for Web Server Applications, verifying a user's Android in-app subscription

时间: 2024-10-12 14:31:43

Using OAuth 2.0 for Web Server Applications, verifying a user's Android in-app subscription的相关文章

【实例图文详解】OAuth 2.0 for Web Server Applications

原文链接:http://blog.csdn.net/hjun01/article/details/42032841        OAuth 2.0 for Web Server Applications, verifying a user's Android in-app subscription 在写本文之前先说些题外话. 前段时间游戏急于在GoolePlay上线,明知道如果不加Auth2.0的校验是不安全的还是暂时略过了这一步,果然没几天就发现后台记录与玩家实际付费不太一致,怀疑有玩家盗刷

利用React Native 从0到1 开发一款兼容IOS和android的APP(仿造京东)

最近有一部电视剧叫做<微微一笑很傻逼>里面有个男猪脚,人们都叫他大神~我觉得吧~大神是相对的~所以~啥事都得谦虚! 好了 今天介绍的是如何从0到1利用React Native开发一款兼容IOS和android的仿造京东的APP,是不是很激动?我保证我说的很通俗易懂,当然,大神请滚蛋,这个不适合你看 ok!扯犊子结束,下面开始! 第一步:打开你的手机JD客户端--我们来分析一下 看到了不~在上面的图就是JD客户端的分析图(ok,我承认这图是盗的),从上面的图我们可知道JDAPP的一个页面的主要结

OWIN OAuth 2.0 Authorization Server

OWIN OAuth 2.0 Authorization Server 源码在上面的地址中可以下载 打开客户端页面http://localhost:38500/ 客户端代码引用了DotNetOpenAuth.OAuth2 public class HomeController : Controller { private WebServerClient _webServerClient; public ActionResult Index() { ViewBag.AccessToken = Re

要用Identity Server 4 -- OAuth 2.0 超级简介

OAuth 2.0 简介 OAuth有一些定义: OAuth 2.0是一个委托协议, 它可以让那些控制资源的人允许某个应用以代表他们来访问他们控制的资源, 注意是代表这些人, 而不是假冒或模仿这些人. 这个应用从资源的所有者那里获得到授权(Authorization)和access token, 随后就可以使用这个access token来访问资源. (这里提到的假冒或模仿就是指在客户端复制一份用户名和密码,从而获取相应的权限). OAuth 2.0是一个开放的协议, 它允许使用简单和标准的方法

OAuth 2.0 认证的原理与实践

摘要: 使用 OAuth 2.0 认证的的好处是显然易见的.你只需要用同一个账号密码,就能在各个网站进行访问,而免去了在每个网站都进行注册的繁琐过程. 本文将介绍 OAuth 2.0 的原理,并基于 Spring Security 和 GitHub 账号,来演示 OAuth 2.0 的认证的过程. 原文同步至https://waylau.com/principle-and-practice-of-oauth2/ 使用 OAuth 2.0 认证的的好处是显然易见的.你只需要用同一个账号密码,就能在

The OAuth 2.0 Authorization Framework-摘自https://tools.ietf.org/html/rfc6749

Internet Engineering Task Force (IETF) D. Hardt, Ed. Request for Comments: 6749 Microsoft Obsoletes: 5849 October 2012 Category: Standards Track ISSN: 2070-1721 The OAuth 2.0 Authorization Framework Abstract The OAuth 2.0 authorization framework enab

[转]An introduction to OAuth 2.0 using Facebook in ASP.NET Core

本文转自:http://andrewlock.net/an-introduction-to-oauth-2-using-facebook-in-asp-net-core/ This is the next post in a series on authentication and authorisation in ASP.NET Core. In this post I look in moderate depth at the OAuth 2.0 protocol as it pertain

谈谈基于OAuth 2.0的第三方认证 [上篇]

对于目前大部分Web应用来说,用户认证基本上都由应用自身来完成.具体来说,Web应用利用自身存储的用户凭证(基本上是用户名/密码)与用户提供的凭证进行比较进而确认其真实身份.但是这种由Web应用全权负责的认证方式会带来如下两个问题: 对于用户来说,他们不得不针对不同的访问Web应用提供不同的用户凭证.如果这些凭证具有完全不同的密码,我们没有多少人能够记得住,所以对于大部分整天畅游Internet的网友来说,我想他们在不同的网站注册的帐号都会采用相同的密码.密码的共享必然带来安全隐患,因为我们不能

构建微服务-使用OAuth 2.0保护API接口

微服务操作模型 基于Spring Cloud和Netflix OSS 构建微服务-Part 1 基于Spring Cloud和Netflix OSS构建微服务,Part 2 在本文中,我们将使用OAuth 2.0,创建一个的安全API,可供外部访问Part 1和Part 2完成的微服务. 关于OAuth 2.0的更多信息,可以访问介绍文档:Parecki - OAuth 2 Simplified 和 Jenkov - OAuth 2.0 Tutorial ,或者规范文档 IETF RFC 674