[SwiftUI] - 구글 로그인 구현
구글 로그인 구현 방법을 구글에서 정리해준 걸 참고하여 작성하였다.
iOS 및 macOS용 Google 로그인 시작하기 | Authentication | Google for Developers
중요: 2024년 5월 1일부터 Apple에서는 GoogleSignIn-iOS와 같이 흔히 사용되는 SDK를 사용하는 iOS 애플리케이션의 개인 정보 보호 매니페스트 및 서명을 요구합니다. 2024년 5월 1일 전에 GoogleSignIn-iOS v7.1.0
developers.google.com
1️⃣ GoogleSignIn 패키지 다운
구글 로그인을 사용하기 위해서 GoogleSignIn이라는 패키지를 다운받아야 한다.
pod 'GoogleSignIn'
공식 문서에 아래와 같이 SwiftUI 사용하는 경우 'Google 계정으로 로그인' 버튼의 포르 확장 프로그램 추가할 수 있다고 하는데 아래의 버튼을 의미한다. 난 커스텀 버튼을 사용하기 위해 아래의 패키지는 다운받지 않았다.
pod 파일에 위의 명령어를 넣어준 후 install 해준다.
2️⃣ OAuth 클라이언트 ID 가져오기
아래에서 OAuth 클라이언트 ID 만들기 버튼을 눌러 프로젝트를 만들어준다.
- 프로젝트 선택
- client와 Bundle ID 설정
다 만들고 나면 아래와 같이 Client ID를 얻을 수 있다.
이 ID는 절대 다른 사람에게 노출되어서는 안되기 때문에 잘 관리해야 한다.
API key 값들 관리하는 방법은 아래를 참고하면 좋을 것 같다.
[Swift] - .xcconfig 파일로 API KEY 숨기기
소셜 로그인 연동 구현하던 중 API KEY를 git에 노출시켜버려,,, git에 노출하지 않으면서 API KEY를 사용하는 방법에 대해 찾아보았다. API KEY를 git에 노출시키지 않기 위한 방법을 알아보자. 1️⃣ .xcc
steelbeartaeng2.tistory.com
다 만들고 난 후 client ID는 프로젝트 내에서 다시 확인 가능하다!! 웹 애플리케이션은 신경 쓸 필요없고 iOS만 신경쓰면 된다!
OAuth client 클릭하면 [client ID, iOS URL 스키마, 생성일] 등 많은 정보를 확인할 수 있다.
3️⃣ 프로젝트에 적용하기
🟡 info.plist 파일 수정
1. info 파일 찾기
2. +를 눌러서 행 추가하고 key값 "GIDClientID", value 값: "client ID"를 넣는다.
🟡 URL Shemes 추가
1. 프로젝트 Target -> Info -> URL Types 찾기
2. +를 누른 후 URL Schemes를 추가한다.
3. client ID와 함께 찾았던 iOS URL 스키마 데이터를 넣어준다.
4️⃣ 구글 로그인 로직 구현
🟡 App 파일에 onOpenURL 추가
.onOpenURL을 사용해야 애플리케이션이 URL을 통해 전달된 정보를 적절히 처리할 수 있다.
이 코드는 주로 앱이 외부 애플리케이션(예: 브라우저)에서 돌아온 후 로그인 흐름을 마무리하기 위해 사용한다.
만약 넣지 추가하지 않는다면, 사용자가 구글 로그인 페이지에서 성공적으로 로그인한 후 앱으로 돌아와도 로그인 결과를 인식하지 못해 사용자가 여전히 로그인되지 않은 상태로 인식될 수 있다.
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
GIDSignIn.sharedInstance.handle(url)
}
}
}
}
🟡 OAuthUserData 구조체
먼저 OAuthUserData라는 구조체를 만들어준다.
로그인 성공 후 받아온 oauthId와 idToken을 처리하기 위해 구조체를 만들었고 이제 로그인 로직을 구현해보자.
struct OAuthUserData {
var oauthId: String = ""
var idToken: String = ""
}
🟡 GoogleOAuthViewModel
- 구글 로그인 실행
GIDSignIn.sharedInstance.signIn을 사용하여 구글 로그인 로직을 실행하고 성공한 경우 현재 사용자의 정보를 확인하는 checkUserInfo() 함수를 실행한다.
func signIn() {
//현재 앱에서 최상위 뷰 컨트롤러를 찾는 부분
guard let presentingViewController = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first?.rootViewController else {
return
}
GIDSignIn.sharedInstance.signIn(//구글 로그인 프로세스를 시작
withPresenting: presentingViewController)
{ _, error in
if let error = error {
self.errorMessage = "error: \(error.localizedDescription)"
}
self.checkUserInfo()//현재 사용자의 정보를 확인하는 로직 실행
}
}
🤔 현재 앱에서 최상위 뷰 컨트롤러를 찾는 이유??
=> 구글 로그인 창을 표시하기 위함이다.
구글 로그인은 사용자가 로그인할 수 있는 화면을 모달로 표시한다. 이 모달 화면은 특정 뷰 컨트롤러에서 표시되어야 하기 때문에 이를 위해 현재 활성화된 최상위 뷰 컨트롤러를 찾아야 한다.
- 유저 정보 확인
현재 구글 로그인 상태를 확인하고, 로그인된 사용자의 정보를 업데이트한다.
로그인된 경우 사용자 정보를 가져와서 저장하며, 로그인되지 않은 경우 에러 메시지를 설정한다.
func checkUserInfo() {
if GIDSignIn.sharedInstance.currentUser != nil {//현재 사용자가 로그인되어 있는지 확인
let user = GIDSignIn.sharedInstance.currentUser
guard let user = user else {
return
}
oauthUserData.givenName = user.profile?.givenName ?? "" //사용자의 이름
oauthUserData.oauthId = user.userID ?? "" //사용자의 고유 ID
oauthUserData.idToken = user.idToken?.tokenString ?? ""//사용자의 ID 토큰
} else {
self.errorMessage = "error: Not Logged In"
}
}
GIDSignIn.sharedInstance.currentUser는 현재 로그인된 사용자를 나타내며, 사용자가 로그인되어 있지 않으면 nil을 반환한다.
- 최종 코드
import GoogleSignIn
import SwiftUI
class GoogleOAuthViewModel: ObservableObject {
@Published var oauthUserData = OAuthUserData()
@Published var errorMessage: String?
@Published var givenName: String?
func checkUserInfo() {
if GIDSignIn.sharedInstance.currentUser != nil {
let user = GIDSignIn.sharedInstance.currentUser
guard let user = user else {
return
}
self.givenName = user.profile?.givenName ?? ""
oauthUserData.oauthId = user.userID ?? ""
oauthUserData.idToken = user.idToken?.tokenString ?? ""
} else {
self.errorMessage = "error: Not Logged In"
}
}
func signIn() {
guard let presentingViewController = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first?.rootViewController else {
return
}
GIDSignIn.sharedInstance.signIn(
withPresenting: presentingViewController)
{ _, error in
if let error = error {
self.errorMessage = "error: \(error.localizedDescription)"
}
self.checkUserInfo()
}
}
func signOut() {
GIDSignIn.sharedInstance.signOut()
}
}
🟡 간단한 버튼 View
import SwiftUI
struct ContentView: View {
@StateObject private var viewModel = GoogleOAuthViewModel()
var body: some View {
VStack {
Button(action: {//버튼 클릭시 구글 로그인 로직 실행
viewModel.signIn()
}) {
Text("Sign In with Google")
.font(.title2)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
if let givenName = viewModel.givenName {
Text("Welcome, \(givenName)")
.padding()
} else {
Text("Not Logged In")
.padding()
}
if let errorMessage = viewModel.errorMessage {
Text("\(errorMessage)")
.foregroundColor(.red)
.padding()
}
}
.padding()
}
}
✋ 최종 결과물 !!
- 로그인 실패한 경우