【全示例通过】防沉迷实名认证系统接口测试代码(包含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实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2