Sign in with Google
基于OAuth 2.0的Google登录Web认证方式。iOS和Android可统一使用。
工作方式
Android:
- 在外部浏览器(或Chrome Custom Tabs)中选择Google账号
- 通过Deep Link接收id_token
- 使用Token调用账号注册(SocialSignIn)或会员转换(SocialChange)API
iOS:
- 通过ASWebAuthenticationSession在应用内显示认证会话
- 通过Deep Link或原生回调接收id_token
- 使用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_uri | https://www.playnanoo.com/oauth2redirect.html | 认证后重定向URL |
| response_type | id_token | OpenID Connect ID Token请求 |
| scope | openid email profile | 请求的用户信息范围 |
| nonce | FGuid::NewGuid() | 防止重放攻击的随机字符串 |
| prompt | select_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"
}
参考
- 账号类型值:
PN_ACCOUNT_GOOGLE = "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环境设置文档。