跳转到主要内容

Sign in with Google

基于OAuth 2.0的Google登录Web认证方式。iOS和Android可统一使用。

工作方式

Android:

  1. 在外部浏览器(或Chrome Custom Tabs)中选择Google账号
  2. 通过Deep Link接收id_token
  3. 使用Token调用账号注册(SocialSignIn)或会员转换(SocialChange)API

iOS:

  1. 通过ASWebAuthenticationSession在应用内显示认证会话
  2. 通过Deep Link或原生回调接收id_token
  3. 使用Token调用账号注册(SocialSignIn)或会员转换(SocialChange)API

Unreal实现

// GoogleSignin.h
#pragma once

#include "CoreMinimal.h"
#include "Http.h"
#include "Json.h"
#include "JsonUtilities.h"
#include "GoogleSignin.generated.h"

DECLARE_DELEGATE_OneParam(FOnGoogleSigninSuccess, const FString&);
DECLARE_DELEGATE_TwoParams(FOnGoogleSigninError, const FString&, const FString&);

UCLASS()
class YOURPROJECT_API UGoogleSignin : public UObject
{
GENERATED_BODY()

private:
static const FString HOST_PLAYNANOO_OAUTH2REDIRECT;
static const FString OAUTH2REDIRECT_SCOPE;

FString ClientId;
FOnGoogleSigninSuccess OnSuccessCallback;
FOnGoogleSigninError OnErrorCallback;

public:
/**
* Google 로그인 시작
*/
UFUNCTION(BlueprintCallable, Category = "PlayNANOO|Account")
void Sign_in_with_Google(const FString& InClientId, FOnGoogleSigninSuccess OnSuccess, FOnGoogleSigninError OnError);

private:
void OnDeepLink(const FString& URL);
FString ExtractIdToken(const FString& URL);
void OpenURLInExternalBrowser(const FString& URL);
void SendTokenToServer(const FString& Token);
};

// GoogleSignin.cpp
#include "GoogleSignin.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/Guid.h"

const FString UGoogleSignin::HOST_PLAYNANOO_OAUTH2REDIRECT = TEXT("https://www.playnanoo.com/oauth2redirect.html");
const FString UGoogleSignin::OAUTH2REDIRECT_SCOPE = TEXT("openid email profile");

void UGoogleSignin::Sign_in_with_Google(const FString& InClientId, FOnGoogleSigninSuccess OnSuccess, FOnGoogleSigninError OnError)
{
ClientId = InClientId;
OnSuccessCallback = OnSuccess;
OnErrorCallback = OnError;

// Deep Link 리스너 등록
FCoreDelegates::ApplicationReceivedDeepLinkDelegate.AddUObject(this, &UGoogleSignin::OnDeepLink);

FString Nonce = FGuid::NewGuid().ToString(EGuidFormats::Digits);

FString AuthUrl = FString::Printf(TEXT("https://accounts.google.com/o/oauth2/v2/auth?client_id=%s&redirect_uri=%s&response_type=id_token&scope=%s&nonce=%s&prompt=select_account&login_hint="),
*ClientId,
*FGenericPlatformHttp::UrlEncode(HOST_PLAYNANOO_OAUTH2REDIRECT),
*FGenericPlatformHttp::UrlEncode(OAUTH2REDIRECT_SCOPE),
*Nonce
);

// 외부 브라우저에서 열기 (403 disallowed_useragent 방지)
OpenURLInExternalBrowser(AuthUrl);
}

void UGoogleSignin::OnDeepLink(const FString& URL)
{
FString Token = ExtractIdToken(URL);
if (!Token.IsEmpty())
{
SendTokenToServer(Token);

// Deep Link 리스너 해제
FCoreDelegates::ApplicationReceivedDeepLinkDelegate.RemoveAll(this);
}
}

FString UGoogleSignin::ExtractIdToken(const FString& URL)
{
// query parameter(?) 또는 fragment(#)에서 id_token 추출
FString ParamString;

if (URL.Contains(TEXT("?")))
{
int32 Index;
URL.FindChar('?', Index);
ParamString = URL.RightChop(Index + 1);
}
else if (URL.Contains(TEXT("#")))
{
int32 Index;
URL.FindChar('#', Index);
ParamString = URL.RightChop(Index + 1);
}

if (ParamString.IsEmpty())
{
return FString();
}

TArray<FString> Parts;
ParamString.ParseIntoArray(Parts, TEXT("&"), true);

for (const FString& Part : Parts)
{
if (Part.StartsWith(TEXT("id_token=")))
{
return Part.RightChop(9); // "id_token=" 길이
}
}

return FString();
}

void UGoogleSignin::OpenURLInExternalBrowser(const FString& URL)
{
#if PLATFORM_ANDROID
// Android 전용 처리
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
jstring URLString = Env->NewStringUTF(TCHAR_TO_UTF8(*URL));
FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis,
FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AndroidThunkJava_LaunchURL", "(Ljava/lang/String;)V", false),
URLString);
Env->DeleteLocalRef(URLString);
}
#else
// 기본 처리 (iOS 등)
FPlatformProcess::LaunchURL(*URL, nullptr, nullptr);
#endif
}

void UGoogleSignin::SendTokenToServer(const FString& Token)
{
// token 값으로 SocialChange 또는 SocialSignIn 진행
// SocialChange(Token, TEXT("GOOGLE"));
// or
// SocialSignIn(Token, TEXT("GOOGLE"));

OnSuccessCallback.ExecuteIfBound(Token);
}

OAuth 2.0 参数

参数说明
client_id从Google Cloud Console获取OAuth Web客户端ID
redirect_urihttps://www.playnanoo.com/oauth2redirect.html认证后重定向URL
response_typeid_tokenOpenID Connect ID Token请求
scopeopenid email profile请求的用户信息范围
nonceFGuid::NewGuid()防止重放攻击的随机字符串
promptselect_account强制显示账号选择
必须完成环境设置

在使用Sign in with Google之前,请参阅Google环境设置文档完成以下设置:

  • 在Google Cloud Console获取OAuth 2.0客户端ID
  • Android/iOS Deep Link设置
  • 注册重定向URI

使用方法

1. 开始Google登录

void UYourClass::StartGoogleSignIn()
{
UGoogleSignin* GoogleSignin = NewObject<UGoogleSignin>();

GoogleSignin->Sign_in_with_Google(
TEXT("Your Google Client Id"),
FOnGoogleSigninSuccess::CreateLambda([](const FString& Token)
{
UE_LOG(LogTemp, Log, TEXT("Google 로그인 성공! Token: %s"), *Token);

// 토큰으로 PlayNANOO API 호출
// SocialSignIn(Token, TEXT("GOOGLE"));
}),
FOnGoogleSigninError::CreateLambda([](const FString& ErrorCode, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Google 로그인 실패: [%s] %s"), *ErrorCode, *Message);
})
);
}

2. 通过Token关联账号

认证完成后,使用SendTokenToServer方法中收到的id_token调用PlayNANOO API:

新登录(SocialSignIn)

void UYourClass::SocialSignIn(const FString& Token, const FString& AccountType)
{
// 구글 계정으로 처음 로그인
// AccountType: "GOOGLE"
}

会员转换(SocialChange)

void UYourClass::SocialChange(const FString& Token, const FString& AccountType)
{
// 비회원에서 구글 회원으로 전환
// AccountType: "GOOGLE"
}
参考

各平台回调处理

Android

  • 通过DeepLink方式接收回调。
  • FCoreDelegates::ApplicationReceivedDeepLinkDelegate上注册回调。
  • 包含Chrome Custom Tabs库时,浏览器将以应用内覆盖形式打开。
  • 如果没有该库,将使用外部Chrome浏览器打开,认证完成后通过Deep Link返回应用。
// Deep Link 리스너 등록
FCoreDelegates::ApplicationReceivedDeepLinkDelegate.AddUObject(this, &UGoogleSignin::OnDeepLink);

iOS

  • 使用ASWebAuthenticationSession在应用内打开认证会话。
  • 通过Deep Link接收回调。
  • 可以使用FCoreDelegates::ApplicationReceivedDeepLinkDelegate以相同方式处理。
Chrome Custom Tabs设置

在Android中要以应用内覆盖形式打开浏览器,需要androidx.browser:browser:1.5.0库。 详细设置方法请参阅Google环境设置文档。