先写个Tips:
1. 这个例子没有检查Game Center用户切换的情况
2. 为了尽量突出验证的过程,汇报显示排行榜等接口也没有列出
3. 如果出现“无法完成所请求的操作,因为 Game Center 未识别此应用程序。”,
a. 请先检查BundleId是否一致
b. 再检查看itunesconnect里是不是没有添加过排行榜或者成就设置,必须要至少添加一条
c. Game Center需要登录你设置ITunesconnect设置过的测试帐号(不用有些资料说的必需新建帐号)
GameCenterUserVerify.h
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>;
@interface GameCenterUserVerify : NSObject
+ (void)Verify;
@end
@interface GameCenterUserManager : NSObject
@end
GameCenterUserVerify.m
#import "GameCenterUserVerify.h"
@implementation GameCenterUserVerify
+ (void)Verify
{
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
if (localPlayer.authenticated)
{
__weak GKLocalPlayer *useLocalPlayer = localPlayer;
[useLocalPlayer generateIdentityVerificationSignatureWithCompletionHandler: ^(NSURL * _Nullable publicKeyUrl,
NSData * _Nullable signature,
NSData * _Nullable salt,
uint64_t timestamp,
NSError * _Nullable error) {
if (error == nil)
{
[self verifyPlayer: useLocalPlayer.playerID // our verify routine: below
publicKeyUrl: publicKeyUrl
signature: signature
salt: salt
timestamp: timestamp];
}
else
{
// GameCenter returned an error; deal with it here.
UnitySendMessage("_GameCenterManager_","IOSGameGameCenterVerifyFail",[[error localizedDescription] UTF8String]);
}
}];
}
else
{
// User is not authenticated; it makes no sense to try to verify them.
UnitySendMessage("_GameCenterManager_","IOSGameGameCenterVerifyFail","Game center not logined.");
}
}
+(void)verifyPlayer: (NSString*) playerID
publicKeyUrl: (NSURL*) publicKeyUrl
signature: (NSData*) signature
salt: (NSData*) salt
timestamp: (uint64_t) timestamp
{
NSDictionary *paramsDict = @{ @"publicKeyUrl": [publicKeyUrl absoluteString],
@"timestamp" : [NSString stringWithFormat: @"%llu", timestamp],
@"signature" : [signature base64EncodedStringWithOptions: 0],
@"salt" : [salt base64EncodedStringWithOptions: 0],
@"playerID" : playerID,
@"bundleID" : [[NSBundle mainBundle] bundleIdentifier]
};
// NOTE: A lot of the code below was cribbed from another SO answer for which I have lost the URL.
// FIXME: <When found, insert other-SO-answer URL here>
// build payload
NSMutableData *payload = [NSMutableData new];
[payload appendData: [playerID dataUsingEncoding: NSASCIIStringEncoding]];
[payload appendData: [[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding: NSASCIIStringEncoding]];
uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
[payload appendBytes: ×tampBE length: sizeof(timestampBE)];
[payload appendData: salt];
// Verify with server
[self verifyPlayerOnServer: payload withSignature: signature publicKeyURL: publicKeyUrl];
}
+ (void) verifyPlayerOnServer: (NSData*) payload withSignature: signature publicKeyURL: (NSURL*) publicKeyUrl
{
// hint courtesy of: http://stackoverflow.com/questions/24621839/how-to-authenticate-the-gklocalplayer-on-my-third-party-server-using-php
NSDictionary *jsonDict = @{ @"data" : [payload base64EncodedStringWithOptions: 0],@"puk":[publicKeyUrl absoluteString],
@"sig":[signature base64EncodedStringWithOptions: 0]};
//NSLog(@"%s [DEBUG] jsonDict: %@", __PRETTY_FUNCTION__, jsonDict);
NSError *error = nil;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject: jsonDict options: 0 error: &error];
if (error != nil)
{
NSLog(@"%s ***** dataWithJson error: %@", __PRETTY_FUNCTION__, error);
UnitySendMessage("_GameCenterManager_","IOSGameGameCenterVerifyFail",[[error localizedDescription] UTF8String]);
}else{
NSString *jsonStr = [[NSString alloc] initWithData:bodyData encoding:NSUTF8StringEncoding];
// NSLog(@"json data:%s",[jsonStr UTF8String]);
UnitySendMessage("_GameCenterManager_","IOSGameGameCenterVerifySuccess",[jsonStr UTF8String]);
}
}
@end
@implementation GameCenterUserManager
extern "C"
{
void CallFromUnity_GameCenterUserVerify()
{
NSLog(@"CallFromUnity_GameCenterUserVerify.");
[GameCenterUserVerify Verify];
}
}
@end
using UnityEngine;
using UnityEngine.SocialPlatforms;
using UnityEngine.SocialPlatforms.GameCenter;
using CodeStage.AntiCheat.ObscuredTypes;
using System.Runtime.InteropServices;
using MiniJSON;
using System.Collections.Generic;
public class GameCenterManager : MonoBehaviour
{
#if UNITY_IOS
[DllImport ("__Internal")]
private static extern void CallFromUnity_GameCenterUserVerify();
#endif
private static GameCenterManager instance;
private static object _lock=new object();
private GameCenterManager(){}
public static GameCenterManager GetInstance()
{
if(instance==null)
{
lock(_lock)
{
if(instance==null)
{
var obj = new GameObject("_GameCenterManager_");
instance = obj.AddComponent<GameCenterManager>();
}
}
}
return instance;
}
private JDKUserModel userModel;
private System.Action<bool> loginCallBack=null;
public void Start()
{
Social.localUser.Authenticate(HandleAuthenticated);
}
public JDKUserModel GetUserModel(){
return userModel;
}
public void Authenticate(System.Action<bool> callback){
// if(Social.localUser.authenticated){
// callback(true);
// }else{
loginCallBack = callback;
Start();
// }
}
private void HandleAuthenticated(bool success)
{
Debug.Log("*** HandleAuthenticated: success = " + success);
if(success)
{
string userInfo = "UserName:" + Social.localUser.userName +"\nUser ID:"+
Social.localUser.id + " \nIsUnderage: "+ Social.localUser.underage;
JDKLog.Log(userInfo);
#if UNITY_IOS && !UNITY_EDITOR
CallFromUnity_GameCenterUserVerify();
#else
if(loginCallBack!=null){
loginCallBack(false);
loginCallBack=null;
}
#endif
}else{
if(loginCallBack!=null){
loginCallBack(false);
loginCallBack=null;
}
}
}
public void IOSGameGameCenterVerifyFail(string errorMsg){
JDKLog.LogError(errorMsg);
if(loginCallBack!=null){
loginCallBack(false);
loginCallBack=null;
}
}
public void IOSGameGameCenterVerifySuccess(string result){
JDKLog.Log(result);
var dict = (IDictionary<string,object>)Json.Deserialize(result);
var data = (string)dict["data"];
var puk = (string)dict["puk"];
var sig = (string)dict["sig"];
//现在可以发送 data/puk/sig 给服务器验证
//我们是先存到一个用户模型,稍候(点击登录按钮后)再发送给服务器端验证
//userModel = new JDKUserModel(Social.localUser.id,Social.localUser.userName,UserChannel.iOSGameCenter,null);
//userModel.SetiOSGameCenterVerifyData(puk, sig, data);
//if(loginCallBack!=null){
// loginCallBack(true);
// loginCallBack=null;
// }
}
}
服务器端我就把关键的验证方法贴出来吧:
from OpenSSL.crypto import verify
from OpenSSL.crypto import load_certificate
from OpenSSL.crypto import FILETYPE_ASN1
import base64
#cert_der对应客户端的puk指向的文件原文,注意客户端给的puk只是url,这里的cert_der需要传入这个url文件的内容,需要自己通过http获取url的文件
#signatureBase64 对应客户端给的sig原文
#dataBase64 对应客户端给的data原文
#user_id 是指客户端的Social.localUser.id,用在这里校验苹果这次签名的是不是这个用户id
def ios_gamecenter_user_verify(cert_der, signatureBase64, dataBase64,user_id):
try:
signature = base64.b64decode(signatureBase64)
data = base64.b64decode(dataBase64)
good_cert = load_certificate(FILETYPE_ASN1, cert_der)
verify(good_cert, signature, data, 'sha256')
user_id=':%sc'%(user_id)
return user_id in data
except:
return False return True