服务端API
一、激励视频服务端回调接收
1. 激励视频服务端验证介绍
服务器端验证是对应用内激励视频广告观看行为进行的额外验证,尽可能的规避欺骗客户端回调来奖励用户的行为。 您可以使用服务器端验证对每一次激励视频广告观看行为进行验证,每次用户看完激励视频广告之后,都会使用您在对接广告时提供的回传网址对此次观看行为进行信息回传。开发者可参考服务端验证信息,自行判断进行奖励的下发。
2. 服务器端验证使用流程
回调 URL: 在用户看完激励广告后,以 GET 方式给开发者通知的地址。例如:
http://www.mytest.com/rewardCallback?userId=&transId=&sign=&extrainfo=&placementId=&price=
3. 服务端验证回传参数说明
服务端验证目前支持以下信息,开发者回调地址中需要包含对应的参数宏。
| 参数宏 | 参数类型 | 说明 |
|---|---|---|
| userId | string | 开发者应用的用户id,可通过SDK 激励视频的 user_id 字段传入 |
| transId | string | 服务端生成的trans_id,具有唯一性,用于标识此次广告观看行为。 开发者可通过此 id 对奖励 发放行为进行去重校验,避免重复发放奖励 |
| placementId | string | 聚合广告位id,观看此次激励视频的聚合广告位id |
| sign | string | 签名校验 |
| extrainfo | string | 通过 SDK的激励视频Request对象的options属性传入,options是个字典,key和value都是可以自定义的 |
| price | string | ecpm,表示为千次展现价格,单位是:分。价格字段需要先使用 urldecode后,再使用base64 decode,最后使用 AES256 算法加解密,密钥为对应聚合广告位的Security Key,填充方式:pkcs5。具体参考下面价格解密示例 |
4. 签名校验生成示例
sha256工具类(Java版本) 引入maven
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
<dependency></dependency>
import org.apache.commons.codec.binary.Hex;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class Sha256Util {
/***
* 利用Apache的工具类实现SHA-256加密
*
* @param str 加密的报文
* @return String
*/
public static String getSHA256Str(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
encodeStr = Hex.encodeHexString(hash);
} catch (Exception e) {
e.printStackTrace();
}
return encodeStr;
}
}
使用
Sha256Util.getSHA256Str(appSecurityKey() + ":" + trans_id)
php版本
$signature = hash('sha256', '密钥' . ':' . $trans_id);
echo $signature;
5. 回调响应
如果回调通过了开发者的全部验证,开发者需要返回以下JSON数据:
{"isValid":true}
| 字段定义 | 字段值或释义 | 字段类型 | 备注 |
|---|---|---|---|
| isValid | true/false | BOOL | 开发者服务端收到请求后判断回调结果,返回给 服务器的校验结果。 |
如果因为网络原因没有收到回调响应,会每隔200毫秒重试3次。
6. 价格解密示例
解密过程说明
开发者需要对收到的价格参数进行解密处理:
1、首先执行urldecode操作
2、使用Base64进行decode操作
3、使用AES/ECB/PKCS5Padding算法对字符串进行解密。解密使用的key取自聚合广告位服务端回调设置中的Security Key
Java 示例
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class TestAes {
public static final String ALGORITHM = "AES";
public static final String SECRET_AES_ECB_MODE = "ECB";
public static final String SECRET_AES_PKCS5_PADDING = "PKCS5Padding";
/**
* 解密
*
* <p>
* byte[] -> byte[]
*
* @param mode 模式
* @param padding 填充
* @param key 密钥
* @param iv 向量,如果模式不用向量,可传递null
* @param ciphertext 密文
* @return 明文
*/
public static byte[] decrypt(String mode, String padding, byte[] key, byte[] iv, byte[] ciphertext) {
return doBytes(mode, padding, key, iv, ciphertext, Cipher.DECRYPT_MODE);
}
private static byte[] doBytes(String mode, String padding, byte[] key, byte[] iv, byte[] input, int opMode) {
Cipher cipher = createCipher(mode, padding, key, iv, opMode);
try {
byte[] output = cipher.doFinal(input);
return output;
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException(e);
}
}
private static Cipher createCipher(String mode, String padding, byte[] key, byte[] iv, int opMode) {
SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
IvParameterSpec ivSpec = null;
if (iv != null) {
ivSpec = new IvParameterSpec(iv);
}
String transformation = ALGORITHM + "/" + mode + "/" + padding;
Cipher cipher;
try {
cipher = Cipher.getInstance(transformation);
cipher.init(opMode, keySpec, ivSpec);
return cipher;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// 原始价格,单位为分
String price = "1000";
// 聚合广告位 Security Key
String secretKey = "xm3k88pem11a5cdg7gqn8agoxnh328m9";
// 平台使用上述key,对price加密、base64 encode、url encode之后得到的加密价格串
String encryptedPrice = "1CIoSLK1Ye2SAAgNB5kiYQ%3D%3D";
// 首先对收到的加密字符串进行 URL decode
String decodedString = URLDecoder.decode(encryptedPrice, "UTF-8");
// 再对URL decode 之后的价格进行Base64 decode
byte[] encryptedBytes = Base64.getDecoder().decode(decodedString.getBytes());
// 进行解密
byte[] decryptedBytes = TestAes.decrypt(SECRET_AES_ECB_MODE, SECRET_AES_PKCS5_PADDING, secretKey.getBytes(),
null, encryptedBytes);
String decryptedPrice = new String(decryptedBytes);
// 解密后,价格应该等于1000
System.out.println(price.equals(decryptedPrice));
}
}
php示例
<?php
function pkcs7_pad($text, $blocksize)
{
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
function pkcs7_unpad($text)
{
$pad = ord($text[strlen($text) - 1]);
if ($pad > strlen($text)) return false;
if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false;
return substr($text, 0, -1 * $pad);
}
function decryptData($input,$key){
$cipher = "aes-256-ecb";
$output=@openssl_decrypt($input, $cipher , $key);
if($output===FALSE)
echo "decrypt data error";
return $output;
}
if (!in_array('aes-256-ecb',openssl_get_cipher_methods())){
echo "please install openssl_encrypt\n e.g.:https://www.php.net/manual/zh/book.openssl.php\n";
die;
}
$secret_key='xm3k88pem11a5cdg7gqn8agoxnh328m9';
//平台加密后的价格
$ency_text='1CIoSLK1Ye2SAAgNB5kiYQ%3D%3D';
//解密后,价格应该等于1000
echo "ori_price : ".decryptData(urldecode($ency_text),$secret_key)."\n";
?>
7. 关于防刷的几点建议
- trans_id是唯一的,要做好唯一性校验。防止重复接收。
- 一定要做好签名验证,防止非法调用。
- SDK奖励回调会有trans_id,客户端上传这个单号到业务系统,接收到服务端回调时,校验是否存在客户端上传,存在再给奖励。
- 业务系统做好风控,设置奖励阈值等。