【实例图文详解】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的校验是不安全的还是暂时略过了这一步,果然没几天就发现后台记录与玩家实际付费不太一致,怀疑有玩家盗刷游戏元宝等,并且真实的走过了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代码如下:

[java] view plain copy print?

  1. /** 获取access_token **/
  2. private static Map<String,String> getAccessToken(){
  3. final String CLIENT_ID = "填入你的client_id";
  4. final String CLIENT_SECRET = "填入你的client_secret";
  5. final String REFRESH_TOKEN = "填入上一步获取的refresh_token";
  6. Map<String,String> map = null;
  7. try {
  8. /**
  9. * https://accounts.google.com/o/oauth2/token?refresh_token={REFRESH_TOKEN}
  10. * &client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&grant_type=refresh_token
  11. */
  12. URL urlGetToken = new URL("https://accounts.google.com/o/oauth2/token");
  13. HttpURLConnection connectionGetToken = (HttpURLConnection) urlGetToken.openConnection();
  14. connectionGetToken.setRequestMethod("POST");
  15. connectionGetToken.setDoOutput(true);
  16. // 开始传送参数
  17. OutputStreamWriter writer  = new OutputStreamWriter(connectionGetToken.getOutputStream());
  18. writer.write("refresh_token="+REFRESH_TOKEN+"&");
  19. writer.write("client_id="+CLIENT_ID+"&");
  20. writer.write("client_secret="+CLIENT_SECRET+"&");
  21. writer.write("grant_type=refresh_token");
  22. writer.close();
  23. //若响应码为200则表示请求成功
  24. if(connectionGetToken.getResponseCode() == HttpURLConnection.HTTP_OK){
  25. StringBuilder sb = new StringBuilder();
  26. BufferedReader reader = new BufferedReader(
  27. new InputStreamReader(connectionGetToken.getInputStream(), "utf-8"));
  28. String strLine = "";
  29. while((strLine = reader.readLine()) != null){
  30. sb.append(strLine);
  31. }
  32. // 取得谷歌回传的信息(JSON格式)
  33. JSONObject jo = JSONObject.fromObject(sb.toString());
  34. String ACCESS_TOKEN = jo.getString("access_token");
  35. Integer EXPIRES_IN = jo.getInt("expires_in");
  36. map = new HashMap<String,String>();
  37. map.put("access_token", ACCESS_TOKEN);
  38. map.put("expires_in", String.valueOf(EXPIRES_IN));
  39. // 带入access_token的创建时间,用于之后判断是否失效
  40. map.put("create_time",String.valueOf((new Date().getTime()) / 1000));
  41. logger.info("包含access_token的JSON信息为: "+jo);
  42. }
  43. } catch (MalformedURLException e) {
  44. logger.error("获取access_token失败,原因是:"+e);
  45. e.printStackTrace();
  46. } catch (IOException e) {
  47. logger.error("获取access_token失败,原因是:"+e);
  48. e.printStackTrace();
  49. }
  50. return map;
  51. }

五. 使用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代码如下:

[java] view plain copy print?

  1. private static Map<String,String> cacheToken = null;//设置静态变量,用于判断access_token是否过期
  2. public static GooglePlayBuyEntity getInfoFromGooglePlayServer(String packageName,String productId, String purchaseToken) {
  3. if(null != cacheToken){
  4. Long expires_in = Long.valueOf(cacheToken.get("expires_in")); // 有效时长
  5. Long create_time = Long.valueOf(cacheToken.get("create_time")); // access_token的创建时间
  6. Long now_time = (new Date().getTime()) / 1000;
  7. if(now_time > (create_time + expires_in - 300)){ // 提前五分钟重新获取access_token
  8. cacheToken = getAccessToken();
  9. }
  10. }else{
  11. cacheToken = getAccessToken();
  12. }
  13. String access_token = cacheToken.get("access_token");
  14. GooglePlayBuyEntity buyEntity = null;
  15. try {
  16. /**这是写这篇博客时间时的最新API,v2版本。
  17. * https://www.googleapis.com/androidpublisher/v2/applications/{packageName}
  18. * /purchases/products/{productId}/tokens/{purchaseToken}?access_token={access_token}
  19. */
  20. String url = "https://www.googleapis.com/androidpublisher/v2/applications";
  21. StringBuffer getURL = new StringBuffer();
  22. getURL.append(url);
  23. getURL.append("/" + packageName);
  24. getURL.append("/purchases/products");
  25. getURL.append("/" + productId   );
  26. getURL.append("/tokens/" + purchaseToken);
  27. getURL.append("?access_token=" + access_token);
  28. URL urlObtainOrder = new URL(getURL.toString());
  29. HttpURLConnection connectionObtainOrder = (HttpURLConnection) urlObtainOrder.openConnection();
  30. connectionObtainOrder.setRequestMethod("GET");
  31. connectionObtainOrder.setDoOutput(true);
  32. // 如果认证成功
  33. if (connectionObtainOrder.getResponseCode() == HttpURLConnection.HTTP_OK) {
  34. StringBuilder sbLines = new StringBuilder("");
  35. BufferedReader reader = new BufferedReader(new InputStreamReader(
  36. connectionObtainOrder.getInputStream(), "utf-8"));
  37. String strLine = "";
  38. while ((strLine = reader.readLine()) != null) {
  39. sbLines.append(strLine);
  40. }
  41. // 把上面取回來的資料,放進JSONObject中,以方便我們直接存取到想要的參數
  42. JSONObject jo = JSONObject.fromObject(sbLines.toString());
  43. Integer status = jo.getInt("purchaseState");
  44. if(status == 0){ //验证成功
  45. buyEntity = new GooglePlayBuyEntity();
  46. buyEntity.setConsumptionState(jo.getInt("consumptionState"));
  47. buyEntity.setDeveloperPayload(jo.getString("developerPayload"));
  48. buyEntity.setKind(jo.getString("kind"));
  49. buyEntity.setPurchaseState(status);
  50. buyEntity.setPurchaseTimeMillis(jo.getLong("purchaseTimeMillis"));
  51. }else{
  52. // 购买无效
  53. buyEntity = new GooglePlayBuyEntity();
  54. buyEntity.setPurchaseState(status);
  55. logger.info("从GooglePlay账单校验失败,原因是purchaseStatus为" + status);
  56. }
  57. }
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. buyEntity = new GooglePlayBuyEntity();
  61. buyEntity.setPurchaseState(-1);
  62. }
  63. return buyEntity;
  64. }

到这里就写完了,如果有什么疑问可以留言。

另外,iOS应用内支付,苹果商店AppStore购买信息校验的博客在这里:http://blog.csdn.net/hjun01/article/details/44039939

时间: 2024-08-26 08:24:29

【实例图文详解】OAuth 2.0 for Web Server Applications的相关文章

Using OAuth 2.0 for Web Server Applications, verifying a user&#39;s Android in-app subscription

在写本文之前先说些题外话. 前段时间游戏急于在GoolePlay上线,明知道如果不加Auth2.0的校验是不安全的还是暂时略过了这一步,果然没几天就发现后台记录与玩家实际付费不太一致,怀疑有玩家盗刷游戏元宝等,并且真实的走过了GooglePlay的所有支付流程完成道具兑换,时间一长严重性可想而知.经过查阅大量google官方文档后把代码补上,并在这里记录下OAuth 2.0 的使用,Google提供了OAuth2.0的好几种使用用途,每种使用方法都有些不同,具体可以看下这篇博客.在这里只写OAu

无线充电技术(四种主要方式)原理与应用实例图文详解

转自网络 原文地址:https://www.cnblogs.com/ydvely521/p/9690176.html

CentOS 7上安装Zabbix Server 3.0 图文详解

转载自 http://www.linuxidc.com/Linux/2016-09/135204.htm CentOS 7上安装Zabbix Server 3.0 图文详解 1.查看系统信息. cat /etc/RedHat-releaseCentOS Linux release 7.0.1406 (Core) uname -a Linux VM_96_155_centos3.10.0-123.el7.x86_64 #1 SMP Mon Jun 30 12:09:22 UTC 2014 x86_

centos7.0 安装日志--图文详解-python开发环境配置

centos7.0发布之后,就下载了everthing的DVD镜像,今天有时间,所以决定在vbox底下体验一番--- 上图: 默认是体验安装,作为一个忠实粉丝,我决定选择直接安装! 这个界面是这次新版本更新后改的,它把以前要下一步.上一步可以修改的操作全部集中到一个页面来,默认选择是下图这样,比如你想修改软件安装选项只要点击相应选项就可以了. 每次你更改安装选项之后,它都会自动从新计算安装源,如果你的选择的资源本地没有,还可以通过网络来安装,默认网络是不启用的,所以我们需要自己手工设置一下网络.

全网最详细使用Scrapy时遇到0: UserWarning: You do not have a working installation of the service_identity module: &#39;cannot import name &#39;opentype&#39;&#39;. Please install it from ..的问题解决(图文详解)

不多说,直接上干货! 但是在运行爬虫程序的时候报错了,如下: D:\Code\PycharmProfessionalCode\study\python_spider\30HoursGetWebCrawlerByPython>cd shop D:\Code\PycharmProfessionalCode\study\python_spider\30HoursGetWebCrawlerByPython\shop>scrapy crawl tb :0: UserWarning: You do not

LNMP编译安装之msyql安装--图文详解

LNMP编译安装之msyql安装--图文详解 1.前言 本次安装采用源码安装,主要资源包从官网下载,次要依赖则使用yum进行安装,本篇只涉及mysql的安装,msyql远程登录,不涉及mysql具体配置.该教程纯属安装,不涉及任何重要知识点,老少皆宜. 2.安装步骤 2.1.下载boost(只下载不安装) wget http://nchc.dl.sourceforge.net/project/boost/boost/1.59.0/boost_1_59_0.tar.gz 2.2.解压bosot t

Android中Canvas绘图之Shader使用图文详解

概述 我们在用Android中的Canvas绘制各种图形时,可以通过Paint.setShader(shader)方法为画笔Paint设置shader,这样就可以绘制出多彩的图形.那么Shader是什么呢?做过GPU绘图的同学应该都知道这个词汇,Shader就是着色器的意思.我们可以这样理解,Canvas中的各种drawXXX方法定义了图形的形状,画笔中的Shader则定义了图形的着色.外观,二者结合到一起就决定了最终Canvas绘制的被色彩填充的图形的样子. 类android.graphics

LVS-DR工作原理图文详解

为了阐述方便,我根据官方原理图另外制作了一幅图,如下图所示:VS/DR的体系结构: 我将结合这幅原理图及具体的实例来讲解一下LVS-DR的原理,包括数据包.数据帧的走向和转换过程. 官方的原理说明:Director接收用户的请求,然后根据负载均衡算法选取一台realserver,将包转发过去,最后由realserver直接回复给用户. 实例场景设备清单: 说明:我这里为了方便,client是与vip同一网段的机器.如果是外部的用户访问,将client替换成gateway即可,因为IP包头是不变的

图文详解crond定时任务

第1章crontd的介绍   1.1crond的含义 crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务. 1.2关于任务调度的分类 Linux下的任务调度分为两类,系统任务调度和用户任务调度. 1.系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘.日志清理等.