网易易盾手游智能反外挂支付安全(服务端)接入文档
一、接入说明
购买支付安全服务后,客户端开始数据上报,服务端通过调用支付安全查询接口获取结果并进行分析。目前仅支持iOS手游。
二、接入步骤
2.1 获取安全凭证
在调用支付安全服务前,需要从【易盾官网-服务管理-已开通业务】获取AppId和AppKey,用于计算接口请求认证签名,见认证参数token生成说明。
2.2 测试联调
接入易盾支付安全SDK,上报数据并调用查询接口获取结果,查看结果是否符合预期响应。
三、接口清单
3.1 支付安全结果查询接口
3.1.1 接口用途
支付安全接口提供查询服务,服务端可通过调用该接口获取支付安全结果,建议支付前和支付后分别调用。
3.1.2 接入须知
- 注意事项:建议设置HTTP请求的超时时间,且建议http超时的情况下,认为检测结果为 “正常”。
3.1.3 使用形式
-
接口地址:http://open-yb.dun.163.com/api/v1/ps/check
-
请求方法:POST
-
请求头:Content-Type:application/json
3.1.4 请求参数
请求参数包括公共鉴权参数,和API私有业务参数,发起请求时都需要填写
- (1) 公共参数
鉴权参数名称 | 类型 | 是否必填 | 最大长度 | 描述 |
---|---|---|---|---|
appId | String | 是 | 10 | appId |
timestamp | long | 是 | 13 | 调用接口当前时间,单位毫秒,防重放 |
nonce | String | 是 | 16 | 随机码,防重放 |
token | String | 是 | 32 | 使用appKey签名的数据,检验权限 |
- (2) 业务参数
参数名称 | 类型 | 必须 | 最大长度 | 描述 |
---|---|---|---|---|
acToken | String | 是 | 256 | 支付安全校验结果查询凭证,由业务前端提交给业务后端 |
account | String | 是 | 256 | 用户唯一标识,如果是手机号or邮箱,支持传入hash值,hash算法:md5(account) |
String | 否 | 64 | 用户的邮箱,如果需要加密,支持传入hash值,hash算法:md5(email) | |
phone | String | 否 | 64 | 默认国内手机号。如有海外手机,需包含国家地区代码,格式为“+447410xxx186(+44即为国家码)”。如果需要加密,支持传入hash值,hash算法:md5(phone) |
ip | String | 是 | 20 | 用户参加活动时的ip |
registerTime | Number | 否 | 13 | 用户的注册时间,单位:毫秒 |
registerIp | String | 否 | 20 | 用户的注册IP |
nickname | String | 否 | 256 | 用户的昵称 |
userLevel | String | 否 | 32 | 用户的等级 |
activityId | String | 否 | 256 | 活动的唯一标识 |
target | String | 否 | 256 | 活动操作的目标,比如:A给B点赞,则target为B。如果target是手机号或邮箱,请提供hash值,hash算法:md5(target) |
orderTime | Number | 否 | 13 | 订单时间,单位:毫秒 |
orderReceipt | String | 否 | 订单收据,已经解析,json字符串 |
3.1.5 请求参数示例
支付前:
{
"acToken": "9ca17ae2e6fbcda170e2e6ee95ce79f7bff7cccd43ab928bb6d55e968f8b84f53ca8eff984ee4be9b683a2f42af0feaec3b92aa9ec8da6ed4bb4b7a6bad54e978a9ab6d44b8ff5a5abfb7b9aec9d8ced5cb598ee9e",
"appId": "A001374634",
"timestamp":${currentTime},
"nonce":111,
"token": "${sign}",
"account": "test@163.com",
"email": "test@163.com",
"phone": "18955566611",
"ip": "183.136.182.141"
}
支付后:(其中orderReceipt字段需要传入的内容为标准JSON字符串,示例中省略了部分字段)
{
"acToken": "9ca17ae2e6fbcda170e2e6ee95ce79f7bff7cccd43ab928bb6d55e968f8b84f53ca8eff984ee4be9b683a2f42af0feaec3b92aa9ec8da6ed4bb4b7a6bad54e978a9ab6d44b8ff5a5abfb7b9aec9d8ced5cb598ee9e",
"appId": "A001374634",
"timestamp":${currentTime},
"nonce":111,
"token": "${sign}",
"account": "test@163.com",
"email": "test@163.com",
"phone": "18955566611",
"ip": "183.136.182.141",
"orderTime": 1632809505530,
"orderReceipt": "{
'environment': 'Sandbox',
'receipt': {
'adam_id': 0,
'app_item_id': 0,
'application_version': 2013,
'bundle_id': 'com.netease.UUIDDemo16',
'download_id': 0,
'in_app':[
{
'original_purchase_date': '2015-05-20 03:51:24 Etc/GMT',
'original_purchase_date_ms': 1632809505530,
'original_transaction_id': 1000000644961768,
...
},
...
]
},
'status': 0
}"
}
3.1.6 响应结果
- 响应数据格式:json如下所示
参数名称 | 类型 | 描述 |
---|---|---|
code | int | 接口调用状态,取值参见:响应返回码。 |
msg | String | 结果说明,如果调用出错,返回错误描述 |
result | Object | 接口返回结果,具体参见各接口说明。 |
- result说明 响应的result结果字段如下
参数名 | 类型 | 描述 |
---|---|---|
action | int | 检测结果:0 正常(放行),10 正常(观察),20 致命(拦截) |
taskId | String | 任务id,与检测请求一一对应,建议保存此值方便以后数据查询 |
hitInfos | List | 命中信息,例如:[{"hitType":5,"hitMsg":"无SDK数据"}], 其中hitType类型为int,含义是命中类型,详见下方“hitType返回码及含义”表, hitMsg类型为String,含义是命中详情,开通正式定制版后支持返回,可定制返回值. 如果同时命中多个类型,则会返回所有风险信息 |
- hitType返回码及含义
hitType值 | 含义描述 |
---|---|
0 | 正常 |
1 | 数据异常,主要表现有数据完整性校验不通过或数据伪造等 |
2 | 行为异常,主要表现有用户的操作行为(鼠标点击/移动等)无法通过行为验证模型等 |
3 | 设备模型,主要表现有设备指纹等信息无法通过设备验证模型等 |
4 | 业务模型,主要表现有撞库、批量操作、违反业务规则等 |
5 | 校验异常,主要表现有数据强校验结果异常或数据伪造等 |
6 | 模拟器,主要表现有安卓端使用手机模拟器的行为 |
7 | 越狱或ROOT,主要表现有iOS系统已越狱或Android系统已root |
8 | 浏览器异常,主要表现有浏览器分辨率等参数异常或遭篡改等 |
9 | IP异常,主要表现有终端IP画像结果为风险IP或高危IP等 |
10 | 黑名单,易盾自有及客户自定义黑名单数据 |
11 | 白名单,易盾自有及客户自定义白名单数据 |
12 | 高危账号,主要表现有团伙账号或异常共享账号等风险账号类型 |
13 | 多开小号,主要表现有批量多开 |
14 | 篡改硬件信息,主要表现为篡改硬件设备参数信息 |
15 | 篡改系统信息,主要表现为篡改系统参数信息 |
16 | 高危设备,主要表现为高危设备画像风险评分,黑产特征设备等类型 |
17 | 群控或云控,主要表现为群控工作室设备或云机 |
18 | 安装修改工具,主要表现有安装Hook修改、Xposed修改,Magisk修改等 |
19 | 虚拟环境,非真实设备访问环境,区别于安卓模拟器,如编辑后台等 |
20 | 脚本工具,黑灰产用于作弊行为的脚本工具 |
3.1.7 响应结果示例
支付安全识别为正常的响应结果:
{
"code":200,
"msg":"ok",
"result":{
"action":0,
"taskId":"698256a52215495ab3c61f14e8e6e844",
"hitInfos":[
{
"hitType":0,
"hitMsg":"正式定制版在这个字段会返回命中详情"
}]
}
}
支付安全识别为异常的响应结果:
{
"code":200,
"msg":"ok",
"result":{
"action":20,
"taskId":"5aecbc658f2e4cfab24b6f4171e5839e",
"hitInfos":[
{
"hitType":5,
"hitMsg":"正式定制版在这个字段会返回命中详情"
},
{
"hitType":6,
"hitMsg":"正式定制版在这个字段会返回命中详情"
}
]
}
}
附录
响应码定义
状态码 | 说明 |
---|---|
200 | 正常 |
400 | 请求参数不合法 |
401 | 未授权或者授权已过期 |
404 | 服务不存在 |
405 | 参数错误 |
410 | 签名验证失败 |
420 | 请求时间戳不正确 |
430 | 重放请求 |
440 | 参数解密失败 |
503 | 服务器内部出现异常 |
4401 | Token验证失败 |
5503 | API未开放,不可使用 |
5509 | API QPS超出限制 |
5709 | API 最小请求频率超出限制,需要降低请求频率 |
认证参数token生成说明
接口使用签名方式进行认证,每一次请求都必须包含签名信息(即参数token),以验证用户身份,防止信息被恶意篡改。具体使用步骤如下。
(1) 申请安全凭证
使用 API 之前,需申请安全凭证,Appkey,请妥善保管,避免泄漏。
(2) 生成签名参数token
-
步骤一:将公共参数,按照参数名ASCII码表升序顺序排序。如参数:foo=1, bar=2,foo_bar=3, baz=4 。排序后的顺序是 bar=2, baz=4, foo=1, foobar=3。
-
步骤二:将排序好的参数名参数值对,拼装成字符串,格式为:key1value1key2value2…。根据上面示例结果为:bar2baz4foo1foobar3 。
-
步骤三:将申请的Appkey,加到上一步拼装的参数字符串之后。
-
步骤四:把步骤三的字符串,采用UTF-8编码,使用 MD5算法(128位长度)对字符串进行摘要计算,得到token参数值(32位十六进制字符串)。
-
代码示例
String token= SignatureUtils.genSignature(appKey, appId, timestamp, nonce);
其中:SignatureUtils.java 如下:
package com.netease.is.clientapi.utils;
import com.google.common.collect.Maps;
import org.apache.commons.codec.digest.DigestUtils;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Map;
/**
* Created by yubaohong on 19-5-22.
*/
public class SignatureUtils {
/**
* 暴露获取token的方法
* @param appKey
* @param appId
* @param timestamp
* @param nonce
* @return
* @throws UnsupportedEncodingException
*/
public static String genSignature(String appKey, String appId, String timestamp, String nonce) throws UnsupportedEncodingException {
Map<String, String>userParams = getTokenParams(appId, timestamp, nonce);
return SignatureUtils.genSignature(appKey, userParams);
}
/**
* 生成签名信息
* @param appKey产品私钥
* @param params 接口请求参数名和参数值map,不包括signature参数名
* @return
*/
private static String genSignature(String appKey, Map<String, String> params) throws UnsupportedEncodingException {
// 1. 参数名按照ASCII码表升序排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 2. 按照排序拼接参数名与参数值
StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append(params.get(key));
}
// 3. 将appKey拼接到最后
sb.append(appKey);
// 4. MD5是128位长度的摘要算法,转换为十六进制之后长度为32字符
return DigestUtils.md5Hex(sb.toString().getBytes("UTF-8"));
}
/**
* 拼接参数
* @param appId
* @param timestamp
* @param nonce
* @return
*/
private static Map<String, String> getTokenParams(String appId, String timestamp, String nonce) {
<String, String> userParams = Maps.newHashMap();
userParams.put("appId", appId);// 固定
userParams.put("timestamp", timestamp);
userParams.put("nonce", nonce);
return userParams;
}
}