google Play iap 服務(wù)端驗證(java)

最近公司要做海外版,需要接入google play.踩了不少坑,特作此文
20180809更新,增加導包,參數(shù)示例數(shù)據(jù)

20190106更新, google jar包v2版本已廢棄,api未變動,更新至v3即可

驗證方法1 rsa簽名驗證

請參考這篇

驗證方法2 請求google api驗證

需要在google developer console申請service account,具體參考這篇

我的做法 我比較diao,兩種都上了(手動滑稽)

其實是因為之前我認為publicKey在客戶端也存在,可能有風險,真正明白后才知道rsa的公匙本來就是可以可以公開的,私匙是有g(shù)oogle有,肯定是安全的,但是代碼已經(jīng)上線了,也浪費不了多少資源,算是雙重保險吧

放碼過來

maven依賴

<dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-androidpublisher</artifactId>
            <version>v3-rev86-1.25.0</version>
        </dependency>

充值接口,在這里進行充值操作

public Map<String, Object> vertifyOrder(
            @RequestParam("purchaseData") String purchaseData,
            @RequestParam("signature") String signature,
            @RequestParam("type") int type) {

攔截器,通過后的請求才會調(diào)用充值接口.key.p12文件是在classpath下

import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Collections;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.SecurityUtils;
import com.google.api.services.androidpublisher.AndroidPublisher;
import com.google.api.services.androidpublisher.AndroidPublisherScopes;
import com.google.api.services.androidpublisher.model.ProductPurchase;
/**
 * @author weilong.wang Created on 2017/12/8
 */
@Aspect
@Component
public class GoogleSignInterceptor {
    private static final Logger logger = LoggerManage.getLogger();
    private static final String SERVICE_ACCOUNT_EMAIL = "{googlePlay.serviceAccountEmail}";
    private static final String PUBLIC_KEY = "{googlePlay.publicKey}";
    private static final String P12_Key = "/key.p12";

    @Around("execution(* xxx.xxx.xxx.vertifyOrder (..))")
    public Object preHandle(ProceedingJoinPoint proceedingJoinPoint) {
        Object[] args = proceedingJoinPoint.getArgs();
        // 獲取參數(shù)
        String signature = null;
        String purchaseData = null;
        try {
            purchaseData = String.valueOf(args[0]);
            signature = String.valueOf(args[1]);
        } catch (NullPointerException e) {
            logger.error("參數(shù)為空");
        }

        // 簽名校驗
        String publicKey = ConfigManager.getProperty(PUBLIC_KEY);
        try {
            boolean isVertify = docheck(purchaseData, signature, publicKey);
            if (isVertify == false) {
                logger.error("簽名校驗失敗publicKey:" + publicKey);
            }
        } catch (Exception e) {
            logger.error("簽名校驗失敗", e);
        }
        // 調(diào)用google api二次校驗
        com.alibaba.fastjson.JSONObject jsonObject;
        try {
            jsonObject = JSON.parseObject(purchaseData);
        } catch (JSONException e) {
            logger.error("purchaseData解析失敗", e);
        }
        String productId = jsonObject.getString("productId");
        String packageName = jsonObject.getString("packageName");
        String purchaseToken = jsonObject.getString("purchaseToken");
        int purchaseState = jsonObject.getIntValue("purchaseState");
        if (purchaseState != 0) {
            logger.error("訂單未支付!");
        }
        
        try {

            HttpTransport transport = GoogleNetHttpTransport
                    .newTrustedTransport();
            PrivateKey privateKey = SecurityUtils.loadPrivateKeyFromKeyStore(
                    SecurityUtils.getPkcs12KeyStore(),
                    this.getClass().getResourceAsStream(P12_Key), "notasecret",
                    "privatekey", "notasecret");
            GoogleCredential credential = new GoogleCredential.Builder()
                    .setTransport(transport)
                    .setJsonFactory(JacksonFactory.getDefaultInstance())
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                    .setServiceAccountScopes(Collections
                            .singleton(AndroidPublisherScopes.ANDROIDPUBLISHER))
                    .setServiceAccountPrivateKey(privateKey).build();
            AndroidPublisher publisher = new AndroidPublisher.Builder(transport,
                    JacksonFactory.getDefaultInstance(), credential).build();
            AndroidPublisher.Purchases.Products products = publisher.purchases()
                    .products();
            AndroidPublisher.Purchases.Products.Get product = products
                    .get(packageName, productId, purchaseToken);

            ProductPurchase purchase = product.execute();
            if (purchase.getPurchaseState() != 0) {
                logger.error("訂單未支付!");
            }
        } catch (Exception e) {
            logger.error("訂單驗證失敗! ", e);
        }

        try {
            return proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            logger.error("系統(tǒng)錯誤", throwable);

        }
    }

    private boolean docheck(String content, String sign, String publicKey)
            throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] encodedKey = Base64.getDecoder().decode(publicKey);
        PublicKey pubKey = keyFactory
                .generatePublic(new X509EncodedKeySpec(encodedKey));
        Signature signature = Signature.getInstance("SHA1WithRSA");
        signature.initVerify(pubKey);
        signature.update(content.getBytes("utf-8"));
        return signature.verify(Base64.getDecoder().decode(sign));
    }
}


前端傳遞的參數(shù),僅供參考,隱私信息已去除

purchaseData={"orderId":"GPA.3330-4532-0035-01959","packageName":"{packageName}","productId":"{productName}","purchaseTime":1533737586295,"purchaseState":0,"developerPayload":"9c97bb8c-1320-4088-a9c8-6ead08325655{productName}","purchaseToken":"hcgakhnoeoiikhjicdlobfkm.AO-J1OykrKdrP6OFvIvNN3P_ZPOP8IlJSqIO95fiDYhWpB8hN6_PX23XtsEjNg8y5-pCM5AjCp8lZ_daGTQOS9HGS20xA7ev3DEqwl5_HQDnvIGTlrr_divZaA8F6zYTkF3lngn2n11j5DswMyqq84Cz6v2JlKwGWg"}&signature=fP3liv/tbrw0PUgQA/iCymOL1l56wBY8bLgqK58ktcIHGgai166ijpg9jbx3QvJ9ljgmwJca/7N/n6jzzADTy49rtpY2sNvaZDrHcwVLXXfRT9tPgMi2NgTGfRGgNw7lwXH9TkewU7rSJmeDznnuEpf8cfO24FgoUi61XZEPFrz8G83O94nQvNUgIirXQZneKmRGYWrZcomuaHmAVP3CtEchvO0AjlhDsbvZIePfkfPOAgR5PWNgwo0YpNS0TZzSErdPCDgJMxRyztisuqQnExSXnjnGtUQDSk4Ohdu4sIoeLDez8gC30yJ+g8dAtuwLw5L+yxXsEYnX7ALZk950Xg==&type=1


FAQ

  1. service account email和p12從哪里得到
    都在谷歌開發(fā)者控臺,仔細搜尋一下.
  2. Socket連接出現(xiàn)問題
    99%都是翻墻問題,最好在一臺國外服務(wù)器上測試,不要本地測.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钉嘹,更是在濱河造成了極大的恐慌始绍,老刑警劉巖觉壶,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件略号,死亡現(xiàn)場離奇詭異摇展,居然都是意外死亡,警方通過查閱死者的電腦和手機瓤球,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門融欧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冰垄,你說我怎么就攤上這事蹬癌。” “怎么了虹茶?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長隅要。 經(jīng)常有香客問我蝴罪,道長,這世上最難降的妖魔是什么步清? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任要门,我火速辦了婚禮,結(jié)果婚禮上廓啊,老公的妹妹穿的比我還像新娘欢搜。我一直安慰自己,他們只是感情好谴轮,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布炒瘟。 她就那樣靜靜地躺著,像睡著了一般第步。 火紅的嫁衣襯著肌膚如雪疮装。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天粘都,我揣著相機與錄音廓推,去河邊找鬼。 笑死翩隧,一個胖子當著我的面吹牛樊展,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播堆生,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼专缠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了顽频?” 一聲冷哼從身側(cè)響起藤肢,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糯景,沒想到半個月后嘁圈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體省骂,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年最住,在試婚紗的時候發(fā)現(xiàn)自己被綠了钞澳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡涨缚,死狀恐怖轧粟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脓魏,我是刑警寧澤兰吟,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站茂翔,受9級特大地震影響混蔼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜珊燎,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一惭嚣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悔政,春花似錦晚吞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至烹卒,卻和暖如春闷盔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旅急。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工逢勾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藐吮。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓溺拱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谣辞。 傳聞我的和親對象是個殘疾皇子迫摔,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容