http://www.vimer.cn/2014/04/google支付接口被刷以及解决方案.html
最近在google play上线的应用内支付被人刷了,用户模拟发起了大量的支付请求,并且全部成功支付。搞得我最近茶饭不思。。今天总算是解决了,和大家分享一下。
我们客户端的支付实现步骤是:
1. app端调用google支付
2. 支付成功后,调用 自己服务器的发货接口,当然发货接口是做了签名校验的。
之所以在app端调用发货,是因为google貌似没有提供服务器端直接回调url的地方,所以才给了恶意用户模拟google返回的机会。
一开始我以为是我们自己的发货接口密钥被破解了,但是后来经过app上报,发现客户端是真实的走过了所有的google支付流程,即google的支付sdk真的返回了成功。
由于不清楚是因为google的密钥泄漏还是攻击者用别的方法实现,所以客户端这边已经没有办法确认是安全的了。
好在google是提供了查询订单的接口的: http://developer.android.com/google/play/billing/gp-purchase-status-api.html
实现的流程在文档中已经写的很清楚了,我这里就不赘述了。
判断的方法也很简单:
1. 判断是否购买成功
2. 判断返回 developerPayload 是否与传入的值一致。最好传入订单号,以防止重放攻击。
实现代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
# -*- coding: utf-8 -*-
import
requests
import
datetime
from
.
vals
import
logger
class
GooglePurchaseChecker
(
object
)
:
"""
google的支付查询
"""
client_id
=
None
client_secret
=
None
refresh_token
=
None
access_token
=
None
access_token_create_time
=
None
access_token_expire_time
=
None
def
__init__
(
self
,
client_id
,
client_secret
,
refresh_token
)
:
self
.
client_id
=
client_id
self
.
client_secret
=
client_secret
self
.
refresh_token
=
refresh_token
def
get_new_access_token
(
self
)
:
"""
通过refresh_token获取access token
"""
base_url
=
'https://accounts.google.com/o/oauth2/token'
data
=
dict
(
grant_type
=
'refresh_token'
,
client_id
=
self
.
client_id
,
client_secret
=
self
.
client_secret
,
refresh_token
=
self
.
refresh_token
,
)
try
:
rsp
=
requests
.
post
(
base_url
,
data
=
data
)
jdata
=
rsp
.
json
(
)
if
'access_token'
in
jdata
:
self
.
access_token
=
jdata
[
'access_token'
]
self
.
access_token_create_time
=
datetime
.
datetime
.
now
(
)
self
.
access_token_expire_time
=
self
.
access_token_create_time
+
datetime
.
timedelta
(
seconds
=
jdata
[
'expires_in'
]
*
2
/
3
)
return
True
else
:
logger
.
error
(
'no access_token: %s'
,
rsp
)
return
False
except
:
logger
.
error
(
'fail'
,
exc_info
=
True
)
return
False
def
should_get_new_access_token
(
self
)
:
"""
判断是否要重新获取access_token
"""
if
not
self
.
access_token
:
return
True
now
=
datetime
.
datetime
.
now
(
)
if
now
&
gt
;
=
self
.
access_token_expire_time
:
return
True
return
False
def
check_purchase
(
self
,
bill_id
,
package_name
,
product_id
,
purchase_token
)
:
"""
判断是否合法
"""
logger
.
error
(
'purchase check start.bill_id: %s'
,
bill_id
)
if
self
.
should_get_new_access_token
(
)
:
if
not
self
.
get_new_access_token
(
)
:
# 如果没有成功获取到access_token,也先认为成功吧
logger
.
error
(
'get_new_access_token fail. bill_id:%s'
,
bill_id
)
return
-
1
url_tpl
=
'https://www.googleapis.com/androidpublisher/v1.1/applications/{packageName}/inapp/{productId}/purchases/{token}'
url
=
url_tpl
.
format
(
packageName
=
package_name
,
productId
=
product_id
,
token
=
purchase_token
,
)
rsp
=
requests
.
get
(
url
,
params
=
dict
(
access_token
=
self
.
access_token
,
)
)
jdata
=
rsp
.
json
(
)
if
'purchaseState'
not
in
jdata
:
logger
.
error
(
'purchase invalid.bill_id: %s jdata: %s'
,
bill_id
,
jdata
)
return
-
2
if
jdata
[
'purchaseState'
]
==
0
and
jdata
[
'developerPayload'
]
==
'DeveloperPayloadITEM%s'
%
bill_id
:
logger
.
error
(
'purchase valid.bill_id: %s jdata: %s'
,
bill_id
,
jdata
)
return
0
logger
.
error
(
'purchase invalid.bill_id: %s jdata: %s'
,
bill_id
,
jdata
)
return
-
3
|
1
|
|
最后感慨一下,以前在腾讯的时候,安全问题有大帮人帮你一起查,所以根本感觉不到什么危险。现在只有自己了,所有的问题都要考虑到,而且一旦处理不好就可能是致命的。