iOS内购 服务端票据验证及漏单引发的思考.
因业务需要实现了APP内购处理,但在过程中出现了部分不可控的因素,导致部分用户反映有充值不成并漏单的情况。
仔细考虑了几个付费安全上的问题,凡是涉及到付费的问题都很敏感,任何一方出现损失都是不能接受的,所以在这里整理一些支付安全的要点分享一下。
支付方式
IAP是指In-App Purchase, 是一种付费方式,而并不是苹果专有的付费方式,在其它平台上也会有不同的实现,这里针对Apple IAP。
说到IAP安全问题,在苹果的IAP流程中有一个比较明显的逻辑漏洞,这个逻辑漏洞是建立在我们处理不当的情况下发生的,会导致己方提供的服务和用户之间出现问题。先看看IAP支付时序图:

支付流程
1.客户端向Appstore请求购买产品(假设产品信息已经取得),Appstore验证产品成功后,从用户的Apple账户余额中扣费。
2.Appstore向客户端返回一段receipt-data,里面记录了本次交易的证书和签名信息。
3.客户端向我们可以信任的服务器提供receipt-data
4.服务器对receipt-data进行一次base64编码
5.把编码后的receipt-data发往itunes.appstore进行验证
6.itunes.appstore返回验证结果给服务器
7.服务器对商品购买状态以及商品类型,向客户端发放相应的道具与推送数据更新通知
这七个步骤实际上是一个很安全的流程了。那问题出在哪里呢?我们谈谈两种苹果IAP的验证模型。
验证方式
1.IAP built-in Model,本地验证
有些APP甚至是网游,都直接跳过了3~7步骤,在第2步拿到receipt-data之后,直接由客户端向itunes.appstore发送验证请求,并且拿到结果,根据结果修改数据。
我们在设计APP的时候都遵循一个真理,“凡是在客户端的数据都是不安全的”,深以为然。如果没有独立服务器辅助验证,这样也就避免不了数据被修改的事实了,是的,你会少赚钱。
不过如果APP也不通过独立服务器验证,而是在客户端验证之后再告知服务器状态让其发放游戏道具,那就太可怕了点。这是IAP built-in Model
那是不是就完全不能让这个过程变得安全了呢?也不是,但这个安全保障只是让修改变得困难而已。苹果官方提供了 Validating Receipts Locally 在客户端对receipt-data进行安全验证,主要是对证书以及签名的合法性验证。如果不想自己写代码验证,也可以借助第三方机构提供的receipt-data验证API,比较著名的有 urbanairship和 beeblex 。
但如果能伪造一个完全合法的receipt-data,是不是一样可以达到欺骗目的。是的,为了绕过Validating Locally,于是黑客开始用自己伪造的receipt-data进行移花接木,所以出现了可以伪造”合法订单”的 in-appstore 。因此这种本地加强验证的方法也不能完全避免IAP攻击。
2.IAP Server Model,服务器验证
而如果我们把验证逻辑移到服务器上,这个过程就变得容易多了。因为不再需要担心receipt-data被伪造的问题。不过就算把步骤4~7在服务器上做了,同样也会产生一些幼稚的逻辑漏洞:
对验证receipt-data的reponse content不进行验证和记录,只根据Product直接发放商品。这样只要客户端不断提交receipt-data,按照正常逻辑你就需要不断验证并且重复发放商品。较为安全的做法是:
在每一次收到receipt-data之后,都把提交的用户账号以及receipt-data中的单号建立映射并记录下来,在每次验证receipt-data时,先判断其是否已经存在。
只要做了这样的验证,整个支付流程都变得明朗起来。
确保receipt-data的成功提交与异常处理
建立在IAP Server Model的基础上,并且我们知道手机网络是不稳定的,在付款成功后不能确保把receipt-data一定提交到服务器。如果出现了这样的情况,那就意味着用户被appstore扣费了,却没收到服务器发放的道具。(这样就引发了漏单)
解决这个问题的方法是在客户端提交receipt-data给我们的服务器,让我们的服务器向苹果服务器发送验证请求,验证这个receipt-data账单的有效性. 在没有收到回复之前,客户端必须要把receipt-data保存好,并且定期或在合理的UI界面触发向服务端发起请求,直至收到服务端的回复后删除客户端的receipt账单记录。
如果是客户端没成功提交receipt-data,那怎么办?就是用户被扣费了,也收到appstore的消费收据了,却依然没收到道具,于是投诉到客服处。
这种情况在以往的经验中也会出现,常见的用户和运营商发生的纠纷。客服向用户索要账号和appstore的收据单号,通过查询itunes-connect看是否确有这笔订单。
如果订单存在,则要联系研发方去查询服务器,看订单号与用户名是否对应,并且是否已经被使用了,做这一点检查的目的是 为了防止恶意用户利用已经使用过了的订单号进行欺骗(已验证的账单是可以再次请求验证的,曾经为了测试,将账单手动发给服务器处理并成功),谎称自己没收到商品。
当然了,如果查不到这个订单号,就意味着这个订单确实还没使用过,手动给用户补发商品即可。
有朋友问怎么通过itunes-connect查看具体订单,itunes-connect中无法直接看到订单信息,可以用以下方法来查询
1.可以通过账单向苹果发送账单验证,有效可以手动补发
2.用自己的服务器的记录账单列表对比
3.利用第三方的TalkingData等交易函数,会自动记录账单数据
建议
为保证审核的通过,需要在客户端或server进行双重验证,即,先以线上交易验证地址进行验证,如果苹果正式验证服务器的返回验证码code为21007,则再一次连接沙盒测试服务器进行验证即可。
在应用提审时,苹果IAP提审验证时是在沙盒环境的进行的,即:苹果在审核App时,只会在sandbox环境购买,其产生的购买凭证,也只能连接苹果的测试验证服务器,如果没有做双验证,需要特别注意此问题,否则会被拒。
其他
在sandbox中验证receipt:https://sandbox.itunes.apple.com/verifyReceipt
在生产环境中验证receipt:https://buy.itunes.apple.com/verifyReceipt
那么如何自动的识别收据是否是sandbox receipt呢?
识别沙盒环境下收据的方法有两种:
- 根据收据字段 environment = sandbox。
- 根据收据验证接口返回的状态码。
如果status=21007,则表示当前的收据为沙盒环境下收据, 进行验证。
苹果反馈的状态码
- 21000 App Store无法读取你提供的JSON数据
- 21002 收据数据不符合格式
- 21003 收据无法被验证
- 21004 你提供的共享密钥和账户的共享密钥不一致
- 21005 收据服务器当前不可用
- 21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
- 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
- 21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
By Hgq
iOS内购 服务端票据验证及漏单引发的思考.的更多相关文章
- iOS In-App Purchase(IAP)内购服务端二次验证注意事项
前端iOS完成对应的商品购买之后,会得到一个Transaction(交易)的数据结构指针,后端实际上只需要这个结构内的一个东西,那就是 transaction.transactionReceipt. ...
- IOS内购支付server验证模式
IOS 内购支付两种模式: 内置模式 server模式 内置模式的流程: app从app store 获取产品信息 用户选择须要购买的产品 app发送支付请求到app store app store ...
- 苹果IOS内购二次验证返回state为21002的坑
项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...
- iOS开发——高级技术&内购服务
内购服务 大家都知道做iOS开发本身的收入有三种来源:出售应用.内购和广告.国内用户通常很少直接 购买应用,因此对于开发者而言(特别是个人开发者),内购和广告收入就成了主要的收入来源.内购营销模式,通 ...
- IOS内购支付服务器验证模式
IOS 内购支付两种模式: 内置模式 服务器模式 内置模式的流程: app从app store 获取产品信息 用户选择需要购买的产品 app发送支付请求到app store app store 处理支 ...
- iOS 内购相关
iOS 内购相关 下面总结一下过往订阅和内购的项目的代码方面的实现细节和注意事项,特别是掉单方面的处理. 后台的协议.商品ID.银行卡.内购类型.沙盒账号测试人员都由运营或者产品在苹果后台中申请处理. ...
- iOS - 内购总结
如果有人以后要在做内购这一块.希望可以好好的阅读这篇文章,虽然不是字字珠玑.但是也是本人亲人趟过了无数的坑,希望可以对大家有所帮助! 下面是在研究工程中遇到的问题(iOS 内购的流程如下 1 ...
- iOS 内购遇到的坑
一.内购沙盒测试账号在支付成功后,再次购买相同 ID 的物品,会提示如下内容的弹窗.您以购买过此APP内购项目,此项目将免费恢复 原因: 当使用内购购买过商品后没有把这个交易事件关,所以当我们再次去购 ...
- Unity苹果(iOS)内购接入(Unity内置IAP)
https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog ...
随机推荐
- 关于Java中Eclipse运行卡顿、未响应,Cpu100%的快速处理办法
1.与近几日我以及我的同事们遇到一件非常奇怪的问题,我们目前在实现一个小程序,但是不知为何,Eclipse突然变得很卡,以为是小问题最后闹到重启都没解决,于是 我开始查找造成这个现象的原因,发现这个程 ...
- 项目记事【SpringMVC-1】:后台接收前端传来的JSON,并转成对象
背景: 最近项目中使用SpringMVC,需要从前端接收JSON格式的请求,在后端自动转成一个与JSON格式相同的对象. 由于是一个老项目,Spring的版本是3.2.7. 问题1:POST or G ...
- 关于Java String 类型转换时null的问题(转)
关于Java String 类型转换时null的问题 开发中经常遇到从集合类List.Map中取出数据转换为String的问题,这里如果处理不好,经常会遇到空指针异常java.lang.NullPoi ...
- org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'username' not found. Available parameters are [1, 0, param1, param2]
Spring+mybatis错误:org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.bi ...
- Markdown 简单指北
Markdown is intended to be as easy-to-read and easy-to-write as is feasible. Markdown 简介 Markdown是一种 ...
- ORACLE SEQUENCE跳号总结
在ORACLE数据库中,序列(SEQUENCE)是使用非常频繁的一个数据库对象,但是有时候会遇到序列(SEQUECNE)跳号(skip sequence numbers)的情形,那么在哪些情形下会 ...
- jQuery绑定事物处理器
绑定与移除1..bind() 绑定事件可以有2个或者3个参数:第一个参数为事件类型 第二个参数为处理函数 第三个为布尔类型 on()事件代替2..delegate() 事件委托,三个参数,第一个为选择 ...
- 线性代数-矩阵-【2】矩阵生成 C和C++实现
矩阵的知识点之多足以写成一本线性代数. 所以我们把矩阵的运算封装成矩阵类.以C++为主进行详解. 点击这里可以跳转至 [1]矩阵汇总:http://www.cnblogs.com/HongYi-Lia ...
- Mybatis --- 创建方法、全局配置
总体介绍:MyBatis实际上是Ibatis3.0版本以后的持久化层框架[也就是和数据库打交道的框架]! 和数据库打交道的技术有: 原生的JDBC技术--->Spring的Jd ...
- 浅析HTTP协议的请求报文和响应报文
1.HTTP协议与报文简介 HTTP(hypertext transport protocol),即超文本传输协议.这个协议详细规定了浏览器和万维网服务器之间互相通信的规则. 而客户端与服务端通信时 ...