【全示例通过】防沉迷实名认证系统接口测试代码(包含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编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain