【全示例通过】防沉迷实名认证系统接口测试代码(包含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 Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依

SpringBoot整合OpenFeign的完整指南

《SpringBoot整合OpenFeign的完整指南》OpenFeign是由Netflix开发的一个声明式Web服务客户端,它使得编写HTTP客户端变得更加简单,本文为大家介绍了SpringBoot... 目录什么是OpenFeign环境准备创建 Spring Boot 项目添加依赖启用 OpenFeig

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

springboot使用Scheduling实现动态增删启停定时任务教程

《springboot使用Scheduling实现动态增删启停定时任务教程》:本文主要介绍springboot使用Scheduling实现动态增删启停定时任务教程,具有很好的参考价值,希望对大家有... 目录1、配置定时任务需要的线程池2、创建ScheduledFuture的包装类3、注册定时任务,增加、删

SpringBoot整合mybatisPlus实现批量插入并获取ID详解

《SpringBoot整合mybatisPlus实现批量插入并获取ID详解》这篇文章主要为大家详细介绍了SpringBoot如何整合mybatisPlus实现批量插入并获取ID,文中的示例代码讲解详细... 目录【1】saveBATch(一万条数据总耗时:2478ms)【2】集合方式foreach(一万条数

IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤及问题解决

《IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决》:本文主要介绍IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决,本文分步骤结合实例给大... 目录步骤 1:创建 Maven Web 项目步骤 2:添加 Spring MVC 依赖1、保存后执行2、将新的依赖

SpringBoot中配置文件的加载顺序解读

《SpringBoot中配置文件的加载顺序解读》:本文主要介绍SpringBoot中配置文件的加载顺序,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot配置文件的加载顺序1、命令⾏参数2、Java系统属性3、操作系统环境变量5、项目【外部】的ap

SpringBoot UserAgentUtils获取用户浏览器的用法

《SpringBootUserAgentUtils获取用户浏览器的用法》UserAgentUtils是于处理用户代理(User-Agent)字符串的工具类,一般用于解析和处理浏览器、操作系统以及设备... 目录介绍效果图依赖封装客户端工具封装IP工具实体类获取设备信息入库介绍UserAgentUtils

Spring 中的循环引用问题解决方法

《Spring中的循环引用问题解决方法》:本文主要介绍Spring中的循环引用问题解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录什么是循环引用?循环依赖三级缓存解决循环依赖二级缓存三级缓存本章来聊聊Spring 中的循环引用问题该如何解决。这里聊

pandas中位数填充空值的实现示例

《pandas中位数填充空值的实现示例》中位数填充是一种简单而有效的方法,用于填充数据集中缺失的值,本文就来介绍一下pandas中位数填充空值的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录什么是中位数填充?为什么选择中位数填充?示例数据结果分析完整代码总结在数据分析和机器学习过程中,处理缺失数