跳到主要内容

登录验证

1、登录流程图

login

2、在线验签

2.1、接口说明

本接口为游戏客户端在接收SDK客户端登录消息后,发送至游戏服务器。再由游戏服务器发送至登录服务器进行登录token有效性验证。

2.2、接口地址

【正式环境】:https://xxxxxxx/Wbsrv/Check_Login_DH.aspx

【测试环境】:http://xxxxxxx/Wbsrv/Check_Login_DH.aspx

域名请联系发行获取(登录域名)

2.3、环境说明

为方便游戏在接入阶段进行调试,及SDK的不定期更新,特启用正式及测试两套环境。

环境的使用需注意,验证接口的正式或测试表示SDK客户端生成token时对应的登录服务器环境,该环境取决于客户端的配置选择。

获取当前客户端处于何种环境有:

1、游戏客户端手动配置(通常应用于游戏母包的配置),可按照以下方法进行切换。

AndroidManifest.xml
<!--SDK环境配置(true:测试环境,false:正式环境)-->
<meta-data android:name="dh_eng" android:value="false" />

2、开放平台在线出包配置(在线出包时需特别注意,不论母包中如何配置dh_eng值,实际出包时都将按照在线配置修改dh_eng值进行输出)

3、游戏服务器根据验证token后返回值(result)判定当前客户端处于何种环境。

如将正式环境生成的token发送至测试环境验证,则result会返回-6,此时该将验证地址修改为测试环境地址再次验证。同理,如果测试环境token发送至正式环境验证,则result会返回-7。

警告

由于SDK客户端的特殊性,目前暂无法在登录成功返回给游戏客户端消息数据中包含环境标识,需游戏客户端自行获取,或根据服务器验证token返回值进行程序自动切换。

注意

测试环境仅允许出现于项目的接入及调试阶段,发布正式版本前,Android包必须通过开放平台进行出包,且关闭测试环境选项。IOS也必须在出包前在本地关闭相关环境配置。

2.4、请求方式

警告

POST

2.5、请求参数说明

信息

请求参数字段名称必须与下列参数大小写保持一致。

token验证不区分区服、设备、以及系统类型(Android、iOS等)

参数及值来源于SDK客户端登录成功后返回给游戏客户端登录数据。

参数名类型说明示例
accountidString账号ID1490014080
appidString产品ID1413829460
logintypeString登录类型LoginType_Quick_Visitor
tokenStringtoken值(登录令牌)ba9939c43a1c43558a252f9b1d3453b0
signString签名(加密规则)4b06a255ab468d231624c078c001aba7

2.6、返回参数说明

信息

返回参数将以JSON格式返回。当验证成功返回值result为1时,data中会返回相应账号信息。部分情况下,resultInfo会返回相应的异常信息。

参数名类型说明
resultInt验证状态(详见下方返回result说明)
resultInfoString验证结果内容
dataJsonObject数据内容
> accountidInt账号ID(角色唯一标识)
> accountString账号(非唯一)
> accountviewString账号缩略(暂未使用)
> tokenStringtoken验证值
> timestampString服务器时间戳
> logintypeString(海外)、Int(国内)验证返回时该字段无效具体请参考SDK客户端回调值为准,建议不要做业务关联
> regionint特殊处理字段,新接入游戏无需处理。
> isRealNameAuthint是否实名(0:未实名,1:已实名)(海外请忽略)
> isAdultint是否成年(0:未成年,1:已成年)(海外请忽略)
> ageint年龄 (海外请忽略)
{
"data":{
"account": "12470082",
"accountid": 2087249778,
"accountview": "124***82",
"age": 18,
"bindphone": 0,
"chanOpenID": "12470082",
"isAdult": 1,
"isRealNameAuth": 1,
"logintype": 6,
"region": 1,
"sign": "83c4ece87bc76bdda4d6f3232a7b5a00",
"timestamp": "1662107094",
"token": "63ab96995cfb4e3d9631229dc87dbcc8",
"userinfo": {}
},
"result": 1,
"resultInfo": "成功"
}
result值值说明
0验证失败
1验证成功
-1参数错误
-2签名错误
-3token过期(测试开发阶段出现请确认客户端与服务端appid是否一致)
-4token已使用
-5token错误
-6当前token为正式环境生成,却被发送至测试环境验证。(请前往正式环境验证)
-7当前token为测试环境生成,却被发送至正式环境验证。(请前往测试环境验证)
-11系统错误

2.7、签名规则及示例

2.7.1、AppKey值说明

加密时使用的AppKey为平台对应AppId所分配的AppKey,文档中为示例AppKey。实际AppKey请联系相应产品对接人员。在MD5签名时,需要相关AppKey参与签名,AppKey为英文字母和数字组成的32位。

2.7.2、签名方式

MD5

2.7.3、参数示例

参与加密参数
accountid = 1490014080
appid = 1413829460
logintype = LoginType_Quick_Visitor
token = ba9939c43a1c43558a252f9b1d3453b0
信息

token验证不区分区服、设备、以及系统类型(Android、iOS等)

参数及值来源于SDK客户端登录成功后返回给游戏客户端登录数据。

2.7.4、签名规则

MD5(accountid+appid+logintype+token+Appkey)

以上参数名称替换为具体参数值,且"+"表示两个字符串的连接符,不要将"+"放入md5加密源串中

2.7.5、签名原文

MD5(14900140801413829460LoginType_Quick_Visitorba9939c43a1c43558a252f9b1d3453b02926cd821ee3479cbd54590ac6bdaa)

其中2926cd821ee3479cbd54590ac6bdaa为示例AppKey

2.7.6、签名结果

4b06a255ab468d231624c078c001aba7(转换为小写),将此结果填入sign参数后提交验证

2.7.7、最终提交数据

查看请求参数说明

3、本地验签

3.1、验签说明

此验签方式仅做为在线验签的备用方案,游戏服务器可在以下两种状态下使用本地验签,无特殊情况请不要使用:

1、使用在线验签时出现网络超时、或者HTTP状态码异常(非200)

2、游戏依赖于token验证机制的断线重连,或者出现大量掉线时用户密集登录。

3.2、验签参数

参数名类型说明
accountidString账号ID(角色唯一标识)
timestamplong当前登录态有效维持到期时间(unix时间戳,1970-1-1开始)
tokenString登录令牌
signStringMD5(accountid+timestamp+token+appkey)

3.2、验签方式

MD5

3.3、签名规则及示例

3.3.1、参数示例

参与加密参数
accountid = 1490014080
timestamp = 1569057445
token = ba9939c43a1c43558a252f9b1d3453b0
信息

token验证不区分区服、设备、以及系统类型(Android、iOS等)

参数及值来源于SDK客户端登录成功后返回给游戏客户端登录数据。

timestamp值(unix时间戳)为当前token值对应的有效维持到期时间。

3.3.2、签名规则

MD5(accountid+timestamp+token+Appkey)

以上参数名称替换为具体参数值,且"+"表示两个字符串的连接符,不要将"+"放入md5加密源串中

3.3.3、签名原文

MD5(14900140801569057445ba9939c43a1c43558a252f9b1d3453b02926cd821ee3479cbd54590ac6bdaa)

其中2926cd821ee3479cbd54590ac6bdaa为示例AppKey

3.3.4、签名结果

a7f44f39dcc7c5cb350da514799c0e05(转换为小写)

3.3.5、数据验证

将以上结果值与SDK客户端登录成功后的sign参数值进行比对,一致则表示验证通过。

4、常见问题

1、登录服务器采用AppKey等加密签名机制验证安全性,请不要将AppKey配置在游戏客户端,并做好保密。

2、登录服务器未设置任何黑白IP名单限制来源请求,如游戏服务器由负责运维,请联系相关运维同事开放防火墙中域名出口白名单,否则会导致在线验证超时。

3、在线验证采用POST请求机制,数据body采用form-data模式。

4、在开发调试阶段,在线验证result返回值为-3(token过期),常见于接口地址环境使用错误,目前登录服务器会以(-6,-7)状态返回,提示应选择正确的接口地址环境。

5、调用代码示例

5.1 java调用示例

    public static void main(String[] args) {
LoginCheckVO loginCheckVO = new LoginCheckVO();
loginCheckVO.setAccountId("1490014080");
loginCheckVO.setAppid("1413829460");
loginCheckVO.setLoginType("LoginType_Quick_Visitor");
loginCheckVO.setToken("ba9939c43a1c43558a252f9b1d3453b0");
String appKey = "2926cd821ee3479cbd54590ac6bdaa";
TestController testController = new TestController();
try {
String sign = testController.generateSign(loginCheckVO, appKey);
loginCheckVO.setSign(sign);
//接口域名根据实际情况替换
String url = "https://xxxxxxxxxx/Wbsrv/Check_Login_DH.aspx";
//验证结果
Boolean checkFlag = testController.checkLogin(url, loginCheckVO);
System.out.println(checkFlag);

} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

/**
* 生成MD5签名
* @param loginCheckVO 登录信息
* @param appKey 签名密钥
* @return string 签名字符串
* @throws NoSuchAlgorithmException
*/
public String generateSign(LoginCheckVO loginCheckVO, String appKey) throws NoSuchAlgorithmException {
//明文拼接 注意拼接顺序
String text = loginCheckVO.getAccountId() + loginCheckVO.getAppid() + loginCheckVO.getLoginType() +
loginCheckVO.getToken() + appKey;
//获取MD5消息摘要实例
MessageDigest md = MessageDigest.getInstance("MD5");
//将输入字符串转换为utf8编码的字节数组并更新消息摘要
byte[] hash = md.digest(text.getBytes(StandardCharsets.UTF_8));
//使用DatatypeConverter将字节数组转换为十六进制字符串
return DatatypeConverter.printHexBinary(hash).toLowerCase();
}

/**
* 登录验证
* @param url 请求链接
* @param loginCheckVO 请求参数
* @return
*/
public Boolean checkLogin(String url, LoginCheckVO loginCheckVO) {
RestTemplate restTemplate = new RestTemplate();
//超时时间可以根据项目自定义
SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(5000);
httpRequestFactory.setReadTimeout(30000);
restTemplate.setRequestFactory(httpRequestFactory);
//设置参数
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>(5);
multiValueMap.set("accountid", loginCheckVO.getAccountId());
multiValueMap.set("appid", loginCheckVO.getAppid());
multiValueMap.set("logintype", loginCheckVO.getLoginType());
multiValueMap.set("token", loginCheckVO.getToken());
multiValueMap.set("sign", loginCheckVO.getSign());
//设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, httpEntity, String.class);
//超时等异常情况
if (ObjectUtil.isNull(response) || !response.getStatusCode().equals(HttpStatus.OK) ||
StrUtil.isBlank(response.getBody())) {
return false;
}
//项目中已经引入fastjson2来实现json反序列化,也可用其他方式
JSONObject jsonObject = JSONObject.parseObject(response.getBody());
if (ObjectUtil.isNull(jsonObject) || !jsonObject.containsKey("result") || jsonObject.getInteger("result") != 1) {
return false;
}
return true;
}

5.2 c#调用示例

    //引用部分
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;

/// <summary>
/// 生成MD5签名
/// </summary>
/// <param name="loginCheckVO">登录信息</param>
/// <param name="appKey">签名密钥</param>
/// <returns>签名字符串</returns>
public string GenerateSign(LoginCheckVO loginCheckVO, string appKey)
{
// 明文拼接 注意拼接顺序
string text = loginCheckVO.AccountId + loginCheckVO.Appid + loginCheckVO.LoginType +
loginCheckVO.Token + appKey;
// 获取MD5消息摘要实例
using (var md5 = System.Security.Cryptography.MD5.Create())
{
// 将输入字符串转换为utf8编码的字节数组并更新消息摘要
byte[] hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(text));
// 将字节数组转换为十六进制字符串
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (byte b in hash)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}

/// <summary>
/// 登录验证
/// </summary>
/// <param name="url">请求链接</param>
/// <param name="loginCheckVO">请求参数</param>
/// <returns>是否登录成功</returns>
public async Task<bool> CheckLoginAsync(string url, LoginCheckVO loginCheckVO)
{
using (var httpClient = new HttpClient { Timeout = TimeSpan.FromMilliseconds(30000) })
using (var content = new MultipartFormDataContent())
{
// 设置参数
content.Add(new StringContent(loginCheckVO.AccountId), "accountid");
content.Add(new StringContent(loginCheckVO.Appid), "appid");
content.Add(new StringContent(loginCheckVO.LoginType), "logintype");
content.Add(new StringContent(loginCheckVO.Token), "token");
content.Add(new StringContent(loginCheckVO.Sign), "sign");
try
{
// 使用CancellationTokenSource来设置5秒连接超时
using (var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromMilliseconds(5000)))
{
HttpResponseMessage response = await httpClient.PostAsync(url, content, cts.Token);
// 异常情况处理
if (!response.IsSuccessStatusCode)
{
return false;
}
string responseBody = await response.Content.ReadAsStringAsync();
if (string.IsNullOrWhiteSpace(responseBody))
{
return false;
}
// 使用Json.NET来反序列化JSON响应
JObject jsonObject = JObject.Parse(responseBody);
if (jsonObject == null || jsonObject["result"] == null || (int)jsonObject["result"] != 1)
{
return false;
}
return true;
}
}
catch (Exception ex)
{
// 处理超时或其他异常情况
Console.WriteLine("Exception: " + ex.Message);
return false;
}
}
}

5.3 PHP调用示例

/**
* 登录验证
* @param $url 请求链接
* @param $loginCheckVO 请求参数
* @param $appKey 签名密钥
* @return bool
*/
function checkLogin($url, $loginCheckVO, $appKey)
{
//生成签名
$loginCheckVO->Sign = md5($loginCheckVO->AccountId . $loginCheckVO->Appid . $loginCheckVO->LoginType .
$loginCheckVO->Token . $appKey);
// 初始化cURL会话
$ch = curl_init();

// 设置参数
$postData = [
'accountid' => $loginCheckVO->AccountId,
'appid' => $loginCheckVO->Appid,
'logintype' => $loginCheckVO->LoginType,
'token' => $loginCheckVO->Token,
'sign' => $loginCheckVO->Sign
];

// 配置cURL选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); // 设置5秒连接超时
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));

// 配置HTTPS选项
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 是否验证对等证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 认证是否来自可信主机
//curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem'); // CA证书文件路径(如果需要)

try {
// 执行POST请求
$response = curl_exec($ch);
// 检查cURL错误
if (curl_errno($ch)) {
throw new Exception(curl_error($ch));
}
// 检查HTTP状态码
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpStatusCode != 200) {
return false;
}
// 解析响应体
$responseBody = json_decode($response);
if (empty($responseBody)) {
return false;
}
// 验证响应结果
if (!isset($responseBody->result) || $responseBody->result != 1) {
return false;
}
return true;
} catch (Exception $ex) {
// 处理异常情况
error_log("Exception: " . $ex->getMessage());
return false;
} finally {
// 关闭cURL会话
curl_close($ch);
}
}

5.4 Golang调用示例

import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
)
// LoginCheckVO 登录信息
type LoginCheckVO struct {
AccountId string
Appid string
LoginType string
Token string
Sign string
}

// GenerateSign 生成MD5签名
func GenerateSign(loginCheckVO *LoginCheckVO, appKey string) (string, error) {
// 明文拼接 注意拼接顺序
text := loginCheckVO.AccountId + loginCheckVO.Appid + loginCheckVO.LoginType + loginCheckVO.Token + appKey
// 获取MD5消息摘要实例
hash := md5.New()
_, err := hash.Write([]byte(text))
if err != nil {
return "", err
}
// 将字节数组转换为十六进制字符串
md5Sum := hex.EncodeToString(hash.Sum(nil))
return md5Sum, nil
}
// CheckLogin 登录验证
func CheckLogin(urlStr string, loginCheckVO *LoginCheckVO) (bool, error) {
client := &http.Client{
Timeout: 30 * time.Second,
}
// 设置参数
data := url.Values{}
data.Set("accountid", loginCheckVO.AccountId)
data.Set("appid", loginCheckVO.Appid)
data.Set("logintype", loginCheckVO.LoginType)
data.Set("token", loginCheckVO.Token)
data.Set("sign", loginCheckVO.Sign)

req, err := http.NewRequest(http.MethodPost, urlStr, bytes.NewBufferString(data.Encode()))
if err != nil {
return false, err
}

// 设置请求头
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()

// 超时等异常情况
if resp.StatusCode != http.StatusOK {
return false, nil
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}

responseBody := string(body)
if responseBody == "" {
return false, nil
}

// 假设你已经引入了某种JSON库来解析响应体
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return false, err
}

if result["result"] == nil || int(result["result"].(float64)) != 1 {
return false, nil
}

return true, nil
}