【全示例通过】防沉迷实名认证系统接口测试代码(包含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

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Mac系统下卸载JAVA和JDK的步骤

《Mac系统下卸载JAVA和JDK的步骤》JDK是Java语言的软件开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源,:本文主要介绍Mac系统下卸载JAVA和JDK的相关资料,需... 目录1. 卸载系统自带的 Java 版本检查当前 Java 版本通过命令卸载系统 Java2. 卸载自定

Python如何去除图片干扰代码示例

《Python如何去除图片干扰代码示例》图片降噪是一个广泛应用于图像处理的技术,可以提高图像质量和相关应用的效果,:本文主要介绍Python如何去除图片干扰的相关资料,文中通过代码介绍的非常详细,... 目录一、噪声去除1. 高斯噪声(像素值正态分布扰动)2. 椒盐噪声(随机黑白像素点)3. 复杂噪声(如伪

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

python使用库爬取m3u8文件的示例

《python使用库爬取m3u8文件的示例》本文主要介绍了python使用库爬取m3u8文件的示例,可以使用requests、m3u8、ffmpeg等库,实现获取、解析、下载视频片段并合并等步骤,具有... 目录一、准备工作二、获取m3u8文件内容三、解析m3u8文件四、下载视频片段五、合并视频片段六、错误

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja