需求描述
快游戲開發者調用帳號登錄接口成功后,會收到 gameAuthSign 字段,代表華為服務器返回的登錄簽名,建議開發者調用“校驗登錄簽名接口”對返回的簽名進行校驗,以此確保登錄結果的准確性。
H5 快游戲:
Runtime 快游戲:
需求分析
校驗登錄簽名的接口文檔如下:
解決方案
服務端 java 代碼如下所示
Loginmain.java(入口文件,傳入參與簽名校驗的參數)
/**
* 本程序可以用於應用服務端發送驗證登錄簽名請求時,使用真實數據測試驗簽結果。
*/
package com.huawei.test;
import java.util.HashMap;
import java.util.Map;
public class Loginmain {
public static void main(String[] args)
{
// CP簽名游戲私鑰
final String CP_AUTH_SIGN_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALQ8JRusTb3swpvmeOXbVjuJmf3e8PMbwhr5670hI8IgrYykszXBNOXCSaysr6m6MFw8zRG4BPDxFmauBYMVWZKpcce/lKfmw8s9DwYXQ+weu ==";
// 游戲服務器提供給CP簽名接口URL
final String requestUri = "https://jos-api.cloud.huawei.com/gameservice/api/gbClientApi";
// CP請求參數
Map<String,String> mockRequestParams = new HashMap<>();
mockRequestParams.put("method","external.hms.gs.checkPlayerSign");
mockRequestParams.put("appId","103573859");
mockRequestParams.put("cpId","900086000034703063");
mockRequestParams.put("ts","1609388135341");
mockRequestParams.put("playerId","1180392374801211");
mockRequestParams.put("playerLevel","1");
mockRequestParams.put("playerSSign","NZWHB7q4cgAvX2L2E9ZCdDnUkUJYrswOFNVAjHMKgMO8SgFRxderjzpRqDrYZv6n0UuJC4MYG1fBCPjW8%2FKJa4%2Bm90j12%2FiyEZz7VwXkIi6Azgk6ILdRW%2BH4lNjINl3th7PN7qo%2F0C%2BNWOFuPXRoCcntm%2FYWOG0Ak1a0keSdLeqRZZ0w9OyyQMkzRKVllL1hOEpVia3CwpEzHnrGe8IrV%2Bchr8ERj4I9oYvU9PTjyyCv%2FTWJ7eF0Xe5Ws3JfdBeR8R1dINj%2Fr%2BSaPViVDBY%2BMvduDGTjMyD5dPRN4QaK%2FYtOFqNPscW%2B0%2FCq18wSsnpihdbC5TmTrDEylThZf5Yt2Q%3D%3D");
GameServiceCallDemo.callGameService(requestUri,mockRequestParams,CP_AUTH_SIGN_PRIVATE_KEY);
}
}
GameServiceCallDemo.java(用於調用簽名校驗接口進行校驗)
package com.huawei.test;
import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.Asserts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class GameServiceCallDemo
{
private static final String RETURN_CODE_SUCCEED = "0";
private static final int HTTP_RESPONSE_STATUS_CODE_OK = 200;
/**
*
* @param requestUri 請求URL
* @param requestParams 請求參數對
* @param cpAuthKey CP側簽名私鑰
*/
public static void callGameService(String requestUri, Map<String,String> requestParams, final String cpAuthKey)
{
requestParams.put("cpSign",generateCPSign(requestParams,cpAuthKey));
// 響應消息中返回參數
Map<String,Object> responseParamPairs = doPost(requestUri,requestParams);
if(responseParamPairs.isEmpty())
{
System.out.println("None response parameter.");
}
else
{
if(RETURN_CODE_SUCCEED.equalsIgnoreCase(getString("rtnCode",responseParamPairs)))
{
System.out.println("rtnCode: " + getString("rtnCode",responseParamPairs));
System.out.println("ts: " + getString("ts",responseParamPairs));
System.out.println("rtnSign: " + getString("rtnSign",responseParamPairs));
}
else
{
System.out.println("rtnCode: " + getString("rtnCode",responseParamPairs));
System.out.println("errMsg: " + getString("errMsg",responseParamPairs));
}
}
}
private static String getString(String key,Map<String,Object> responseParamPairs)
{
Asserts.notNull(responseParamPairs, "responseParamPairs");
Object value = responseParamPairs.get(key);
if (value == null) {
return null;
}
return value.toString();
}
/**
* 創建跳過SSL驗證的httpClient實例,https://jos-api.cloud.huawei.com/gameservice/api/gbClientApi這個地址暫時沒有添加SSL證書,所以需要跳過SSL驗證
*/
private static CloseableHttpClient getIgnoeSSLClient() throws Exception {
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
return true;
}
}).build();
//創建httpClient
CloseableHttpClient client = HttpClients.custom().setSSLContext(sslContext).
setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
return client;
}
private static Map<String,Object> doPost(String url, Map<String, String> paramaters)
{
HttpPost httpReq = new HttpPost(url);
// 創建無需SSL驗證的httpClient實例.
CloseableHttpClient httpclient = null ;
try {
httpclient = getIgnoeSSLClient();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try
{
if (paramaters != null)
{
List<NameValuePair> paramPairs = new ArrayList<NameValuePair>();
BasicNameValuePair bnv;
for (Map.Entry<String, String> entry : paramaters.entrySet())
{
bnv = new BasicNameValuePair(entry.getKey(), entry.getValue());
paramPairs.add(bnv);
}
httpReq.setEntity(new UrlEncodedFormEntity(paramPairs, "UTF-8"));
}
Map<String,Object> responseParamPairs = new HashMap<>();
HttpResponse resp = httpclient.execute(httpReq);
if(null != resp && HTTP_RESPONSE_STATUS_CODE_OK == resp.getStatusLine().getStatusCode())
{
responseParamPairs = JSON.parseObject(EntityUtils.toString(resp.getEntity()));
}
return responseParamPairs;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try
{
httpclient.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
private static String sign(byte[] data, String privateKey)
{
try
{
byte[] e = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(e);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(privateK);
signature.update(data);
String signs = Base64.encodeBase64String(signature.sign());
return signs;
}
catch (Exception var)
{
System.out.println("SignUtil.sign error." + var);
return "";
}
}
/**
* 根據參數Map構造排序好的參數串
*
* @param params
* @return
*/
private static String format(Map<String, String> params)
{
StringBuffer base = new StringBuffer();
Map<String, String> tempMap = new TreeMap<String, String>(params);
// 獲取計算nsp_key的基礎串
try
{
for (Map.Entry<String, String> entry : tempMap.entrySet())
{
String k = entry.getKey();
String v = entry.getValue();
base.append(k).append("=").append(URLEncoder.encode(v, "UTF-8")).append("&");
}
}
catch (UnsupportedEncodingException e)
{
System.out.println("Encode parameters failed.");
e.printStackTrace();
}
String body = base.toString().substring(0, base.toString().length() - 1);
// 空格和星號轉義
body = body.replaceAll("\\+", "%20").replaceAll("\\*", "%2A");
return body;
}
private static String generateCPSign(Map<String,String> requestParams,final String cpAuthKey)
{
// 對消息體中查詢字符串按字典序排序並且進行URLCode編碼
String baseStr = format(requestParams);
// 用CP側簽名私鑰對上述編碼后的請求字符串進行簽名
String cpSign = sign(baseStr.getBytes(Charset.forName("UTF-8")), cpAuthKey);
return cpSign;
}
}
欲了解更多詳情,請參見:
H5 快游戲登錄介紹:
runtime 快游戲登錄介紹: