【全示例通过】防沉迷实名认证系统接口测试代码(包含Golang和Java版本)

本文主要是介绍【全示例通过】防沉迷实名认证系统接口测试代码(包含Golang和Java版本),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

下面的代码以及置顶文件使用并修改了作者:jspp@qq.com的开源代码,只作学习使用,侵删

背景:

在接入Taptap的防沉迷实名认证前,需要先通过国家防沉迷实名认证系统的接口测试,要求全部示例通过才能允许使用接口:
在这里插入图片描述

Java版本核心代码

接口加密需要使用密钥对请求报文体数据进行AES-128/GCM + BASE64算法加密

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;public class AESUtil {//    这个算法在我这边跑不起来,所以换成了下面的那个方法
//    public static String  encrypt(String content, String key) {
//        try {
//            byte[] hexStr = hexToByteArray(key);
//            //加密算法:AES/GCM/PKCS5Padding
//         // 初始化加密算法
//            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//            SecretKeySpec skeySpec = new SecretKeySpec(hexStr, "AES");
//            //随机生成iv 12位
//            byte[] iv = RandomStringUtils.random(12).getBytes();
//            //数据加密, AES-GCM-128
//            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new GCMParameterSpec(128, iv));
//            byte[] encrypted = cipher.doFinal(content.getBytes());          //数据加密
//            //iv+加密数据 拼接  iv在前,加密数据在后
//            ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encrypted.length);
//            byteBuffer.put(iv);
//            byteBuffer.put(encrypted);
//            byte[] cipherMessage = byteBuffer.array();
//            //java.util.Base64
//            return java.util.Base64.getEncoder().encodeToString(cipherMessage);
//
//        } catch (Exception e) {
//        	e.printStackTrace();
//            throw new RuntimeException(e);
//        }
//    }public static String encrypt(String cleartext,String key) {try {byte[] hexStr = hexToByteArray(key);SecretKeySpec skeySpec = new SecretKeySpec(hexStr, "AES");// encoding format needs thoughtbyte[] clearTextbytes = cleartext.getBytes("UTF-8");final SecureRandom secureKeyRandomness = SecureRandom.getInstanceStrong();final KeyGenerator AES_keyInstance = KeyGenerator.getInstance("AES");AES_keyInstance.init(128, secureKeyRandomness);final Cipher AES_cipherInstance = Cipher.getInstance("AES/GCM/NoPadding");AES_cipherInstance.init(Cipher.ENCRYPT_MODE, skeySpec);byte[] encryptedText = AES_cipherInstance.doFinal(clearTextbytes);byte[] iv = AES_cipherInstance.getIV();byte[] message = new byte[12 + clearTextbytes.length + 16];System.arraycopy(iv, 0, message, 0, 12);System.arraycopy(encryptedText, 0, message, 12, encryptedText.length);return java.util.Base64.getEncoder().encodeToString(message);} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}public static byte[] hexToByteArray(String hex) {int l = hex.length();byte[] data = new byte[l / 2];for (int i = 0; i < l; i += 2){data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)+ Character.digit(hex.charAt(i + 1), 16));}return data;}}

使用SHA256算法对待加密字符串进行计算


import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class Sha256Utils {/*** 利用java原生的类实现SHA256加密* @param str 加密后的报文* @return*/public static String getSHA256(String str){MessageDigest messageDigest;String encodestr = "";try {messageDigest = MessageDigest.getInstance("SHA-256");messageDigest.update(str.getBytes("UTF-8"));encodestr = byte2Hex(messageDigest.digest());} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}return encodestr;}/*** 将byte转为16进制* @param bytes* @return*/private static String byte2Hex(byte[] bytes){StringBuffer stringBuffer = new StringBuffer();String temp = null;for (int i=0;i<bytes.length;i++){temp = Integer.toHexString(bytes[i] & 0xFF);if (temp.length()==1){stringBuffer.append("0");}stringBuffer.append(temp);}return stringBuffer.toString();}}

Golang版本核心代码

package authenimport ("crypto/aes""crypto/cipher""crypto/rand""crypto/sha256""encoding/base64""encoding/hex""encoding/json""fmt""io""net/http""time""github.com/go-resty/resty/v2"//PS:还引入了一个funs库,其实就是对Json接口的封装而已,自己补一下
)// 防沉迷全局配置
var (APPID     string = "换成你自己的"SECRETKEY string = "换成你自己的"BIZID     string = "换成你自己的"// QUERYURL string = "https://api.wlc.nppa.gov.cn/idcard/authentication/query"QUERYURL    string = "http://api2.wlc.nppa.gov.cn/idcard/authentication/query"CHECKURL    string = "https://api.wlc.nppa.gov.cn/idcard/authentication/check"LOGINOUTURL string = "http://api2.wlc.nppa.gov.cn/behavior/collection/loginout"
)func aesGCMEncrypt(plainText string, key string) (string, error) {// 密钥需要解码decodekey, _ := hex.DecodeString(key)block, err := aes.NewCipher(decodekey)if err != nil {return "", err}aesGcm, err := cipher.NewGCM(block)if err != nil {return "", err}// 向量nonce := make([]byte, aesGcm.NonceSize())if _, err := io.ReadFull(rand.Reader, nonce); err != nil {return "", err}cipherText := aesGcm.Seal(nonce, nonce, []byte(plainText), nil)// encode as base64 stringencoded := base64.StdEncoding.EncodeToString(cipherText)return encoded, nil
}// Generates a SHA256 hash for the given string.
func GetSHA256(str string) string {hasher := sha256.New()hasher.Write([]byte(str))return hex.EncodeToString(hasher.Sum(nil))
}type LoginoutAction struct {No int    `json:"no"` //在批量模式中标识一条行为数据,取值范围 1-128Si string `json:"si"` //一个会话标识只能对应唯一的实名用户,一个实名用户可以拥有多个会话标识;同一用户单次游戏会话中,上下线动作必须使用同一会话标识上报Bt int    `json:"bt"` //游戏用户行为类型 0:下线 1:上线Ot int64  `json:"ot"` //行为发生时间戳,单位秒Ct int    `json:"ct"` //用户行为数据上报类型 0:已认证通过用户 2:游客用户Di string `json:"di"` //游客模式设备标识,由游戏运营单位生成,游客用户下必填Pi string `json:"pi"` //已通过实名认证用户的唯一标识,已认证通过用户必填
}type LoginoutResponse struct {ErrCode int          `json:"errcode"`ErrMsg  string       `json:"errmsg"`Data    LoginoutData `json:"data"`
}type LoginoutData struct {Results []LoginoutResult `json:"results"`
}type LoginoutResult struct {No      int    `json:"no"` //条目编码ErrCode int    `json:"errcode"`ErrMsg  string `json:"errmsg"`
}// 游戏用户行为数据上报接口
func Loginout(datas []LoginoutAction) *LoginoutResponse {timestamp := time.Now().UnixMilli()timestampStr := fmt.Sprintf("%d", timestamp)//TODO 这里能直接传入Json后的datas吗collections := make([]map[string]interface{}, len(datas))for i, entry := range datas {collections[i] = map[string]interface{}{"no": entry.No,"si": entry.Si,"bt": entry.Bt,"ot": entry.Ot,"ct": entry.Ct,"di": entry.Di,"pi": entry.Pi,}}data := map[string]interface{}{"collections": collections,}dataBytes, err := json.Marshal(data)if err != nil {fmt.Println("Loginout 数据Json化失败", err)return nil}dataStr := string(dataBytes)// fmt.Println("数据Json", dataStr)encryptedData, err := aesGCMEncrypt(dataStr, SECRETKEY)if err != nil {fmt.Println("Loginout 请求体加密失败", err)return nil}requestData := map[string]string{"data": encryptedData,}requestJson, err := json.Marshal(requestData)if err != nil {fmt.Println("Loginout 请求体Json化失败:", err)return nil}requestStr := string(requestJson)// fmt.Println("请求体Json", requestStr)signStr := SECRETKEY + "appId" + APPID + "bizId" + BIZID + "timestamps" + timestampStr + requestStr// fmt.Println("待签字符串", signStr)sign := GetSHA256(signStr)// fmt.Println("签名", sign)client := resty.New()client.SetTimeout(time.Duration(5 * time.Second))r, err := client.R().SetHeaders(map[string]string{"appId":        APPID,"bizId":        BIZID,"timestamps":   timestampStr,"sign":         sign,"Content-Type": "application/json",}).SetBody(requestData).Post(LOGINOUTURL)if err != nil {fmt.Println("Error:", err)return nil}sc, err := funs.GetStructByJsonE(&LoginoutResponse{}, r.String())if err != nil {fmt.Println("Loginout Parse Error:", err)return nil}return sc.(*LoginoutResponse)
}type AuthenResponse struct {ErrCode int        `json:"errcode"`ErrMsg  string     `json:"errmsg"`Data    AuthenData `json:"data"`
}type AuthenData struct {Result AuthenResult `json:"result"`
}type AuthenResult struct {Status int    `json:"status"` //认证结果 0:认证成功 1:认证中 2:认证失败Pi     string `json:"pi"`     //已通过实名认证用户的唯一标识
}// 实名认证结果查询接口
// ai: 游戏id
func Query(ai string) *AuthenResponse {URL := QUERYURL + "?ai=" + ai// fmt.Println("查询URL", URL)timestamp := time.Now().UnixMilli()timestampStr := fmt.Sprintf("%d", timestamp)signStr := SECRETKEY + "ai" + ai + "appId" + APPID + "bizId" + BIZID + "timestamps" + timestampStr// fmt.Println("待签名字符串", signStr)sign := GetSHA256(signStr)// fmt.Println("签名", sign)// Send the request.client := resty.New()client.SetTimeout(time.Duration(5 * time.Second))r, err := client.R().SetHeaders(map[string]string{"appId":        APPID,"bizId":        BIZID,"timestamps":   timestampStr,"sign":         sign,"Content-Type": "application/json",}).Get(URL)if err != nil {fmt.Println("Query Error:", err)return nil}sc, err := funs.GetStructByJsonE(&AuthenResponse{}, r.String())if err != nil {fmt.Println("Query Parse Error:", err)return nil}return sc.(*AuthenResponse)
}// 实名认证接口
// ai:游戏id
// name:姓名
// idNum:身份证
func Check(ai, name, idnum string) *AuthenResponse {dataObj := map[string]string{"ai":    ai,"name":  name,"idNum": idnum,}// fmt.Println("原始数据:", dataObj)dataBytes, err := json.Marshal(dataObj)if err != nil {fmt.Println("Check 原始数据Json化失败:", err)return nil}dataJson := string(dataBytes)// fmt.Println("Json:", dataJson)encryptedData, err := aesGCMEncrypt(dataJson, SECRETKEY)if err != nil {fmt.Println("Check 数据加密失败:", err)return nil}// fmt.Println("数据加密:", encryptedData)requestData := map[string]string{"data": encryptedData,}requestJson, err := json.Marshal(requestData)if err != nil {fmt.Println("Check 请求体Json化失败:", err)return nil}requestStr := string(requestJson)// fmt.Println("序列化:", requestStr)timestamp := time.Now().UnixMilli()timestampStr := fmt.Sprintf("%d", timestamp)signStr := SECRETKEY + "appId" + APPID + "bizId" + BIZID + "timestamps" + timestampStr + requestStr// fmt.Println("待签字符串:", signStr)sign := GetSHA256(signStr)// fmt.Println("计算签名:", sign)// Send the request.client := resty.New()client.SetTimeout(time.Duration(5 * time.Second))r, err := client.R().SetHeaders(map[string]string{"appId":        APPID,"bizId":        BIZID,"timestamps":   timestampStr,"sign":         sign,"Content-Type": "application/json;charset=utf-8",}).SetBody(requestStr).Post(CHECKURL)if err != nil {fmt.Println("Check Error:", err)if err == http.ErrHandlerTimeout {return Query(ai)}return nil}sc, err := funs.GetStructByJsonE(&AuthenResponse{}, r.String())if err != nil {fmt.Println("Check Parse Error:", err)return nil}return sc.(*AuthenResponse)
}

坑爹的点:

  1. 接口测试中使用的AppId、密钥等并非Taptap那边的数据,而是网络游戏防沉迷实名认证系统中的相关数据:
    在这里插入图片描述
  2. 测试数据要尽可能多地使用《网络游戏防沉迷实名认证系统测试系统说明》中的所有数据,数据集一定要够多,有部分数据是有规律的,可以类推更多的其他数据,加大训练集,比如这些:
    在这里插入图片描述
  3. 出现错误码:1005 SYS REQ IP ERROR (接口请求IP地址非法),应该是在网络游戏防沉迷实名认证系统的IP白名单中没有填入公网IP:
    在这里插入图片描述
  4. 出现错误码:1007 SYS REQ EXPIRE ERROR (接口请求过期) ,原因是传入的timestamp没有算东八区的时间戳(单位毫秒),在Java中比较简单,直接用System.currentTimeMillis(),但是其他编程语言要记得算上时区
  5. 出现错误码:1011 SYS REQ PARTNER AUTH ERROR(接口请求方身份核验错误),原因是上面提到的接口加密算法(AES-128/GCM + BASE64加密)或者 SHA256算法加签算法有问题,可以直接参考一下上述Java版的代码
  6. 出现错误码:1012 SYS REQ PARAM CHECK ERROR (接口请求报文核验失败),看一下自己报文的拼接是否出现了问题,要严格按照文档的格式来拼接,详情可以看我置顶文件,里面有整个maven项目
  7. 出现错误码:4002 TEST TASK NOT EXIST (测试任务不存在),原因是某一项测试数据已经通过了,这个测试数据就不能再用了,也有可能是你的测试码过期了,更新一下测试码即可
    在这里插入图片描述

这篇关于【全示例通过】防沉迷实名认证系统接口测试代码(包含Golang和Java版本)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/373599

相关文章

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

MySQL中between and的基本用法、范围查询示例详解

《MySQL中betweenand的基本用法、范围查询示例详解》BETWEENAND操作符在MySQL中用于选择在两个值之间的数据,包括边界值,它支持数值和日期类型,示例展示了如何使用BETWEEN... 目录一、between and语法二、使用示例2.1、betwphpeen and数值查询2.2、be

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加