일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Image Processing
- Stream
- ARM
- BAEKJOON
- system hacking
- Flutter
- 파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습
- rao
- C++
- MATLAB
- MDP
- Dreamhack
- PCA
- BFS
- ML
- bloc
- Kaggle
- Algorithm
- pytorch
- Got
- FastAPI
- 영상처리
- 백준
- DART
- study book
- Widget
- BOF
- fastapi를 사용한 파이썬 웹 개발
- Computer Architecture
- llm을 활용 단어장 앱 개발일지
- Today
- Total
Bull
[Dev] LLM을 활용 단어장 앱 개발일지 009: 구글 OAuth 인증, 로그인 페이지 본문
서론과 결론을 미리..
단어에 대한 기능은 거의 완성했다. 아직 완성시킬 기능들(채팅서랍, 스트릭, 태그 찾기 등)을 모두 완성하지 못했지만 로그인 기능을 구현하고 싶었다. 사용자 인증 서비스는 파이어베이스 서드파티를 이용할 것이다.그런데 이메일이 하닌 구글 OAuth 인증을 통해서 가입을 구현하고 싶었다.
결론부터 말하자면 구현은 성공했는데 User 모델을 만들고 나니 여러 Bloc과 통신해야할지 user bloc만으로 모든 상태를 관리할지 지식이 부족했었다.하지만 user bloc으로 모든 걸 구현하면 bloc 패턴에 적합하지 않을 거 같았다. 그리고 아직 로컬DB 밖에 하지 않았기 때문에 서버 DB를 구현해야 한다. 그래서 나는 로그인 인증만 하고 아직 완성안된 기능들을 먼저 만들어야겠다고 생각했다. 로그인 OAuth 기능은 꼭 해보고 싶었기 때문에 먼저했다.
파이어베이스에 이메일만들어지는 것과 uid 를 불러올 수 있게됐으니 user 모델은 나중에 만들면 된다. 어차피 지금 user 모델을 만들어서 한다해도 기능 추가할 때마다 번거롭게 user 구조를 재설정해야 하기 때문에 남은 기능 먼저 구현해보겠다. 서론과 결론은 여기까지고 이제 대략적으로 한걸 작성해보겠다.
로그인 페이지
우선 간단하게 아무앱이나 켜서 비슷하게 만들었다. 앱 로고와 OAuth를 이용한 계정으로 계속하기 버튼. 그리고 저작권 표시. 바탕도 어떤 색으로 칠해져있었지만 이상한 거 같아서 일단 하얀색으로 했다. 아직 앱 이름이랑 로고를 안만들어서 플러터 로고사용함. 만들어야하는데 뭐로해야할지 모르겠다 ㅋㅋ
flutter_signin_button
패키지를 사용했다. 코드는 다음과 같다.
import 'package:flutter/material.dart';
import 'package:flutter_signin_button/flutter_signin_button.dart';
import '../services/auth_service.dart';
import 'package:go_router/go_router.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FlutterLogo(size: 100),
Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 50, vertical: 2),
child: SizedBox(
width: double.infinity,
height: 50,
child: SignInButton(
Buttons.Apple,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(2),
),
elevation: 1.5,
text: 'Apple 계정으로 계속하기',
onPressed: () async {
AuthService.signOut();
},
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 50, vertical: 2),
child: SizedBox(
width: double.infinity,
height: 50,
child: SignInButton(
Buttons.Google,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
elevation: 1.5,
text: 'Google 계정으로 계속하기',
onPressed: () async {
await AuthService.signInWithGoogleAndSendToken();
final isSignedIn = await AuthService.isUserSignedIn();
if (isSignedIn) {
context.go('/chat');
}
},
),
),
),
Padding(
padding: EdgeInsets.only(top: 50),
child: Text(
'Copyrightⓒ 2024, All rights reserved by bull_',
style: TextStyle(
color: Colors.grey[400],
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
);
}
}
애플 버튼은 임시로 토큰을 삭제하고 로그아웃하게 만들었다.
OAuth 생성 및 파이어베이스 설정
우선 전체적인 흐름은 다음과 같다.
OAuth 생성
-> 파이어베이스 Authentication에 ID 정보 전달
-> Flutter 코드에서 google_sign_in
패키지와 파이어베이스를 이용해서 코드 작성
OAuth
난 사실 OAuth가 뭔지 몰랐다. 그래서 "구글 계정 연동" 이라고 찾아봤다.그렇게 검색하면 무조건 파이어베이스 이메일 생성하는 방법밖에 나오지 않았다. 그래도 몇몇 OAuth로 하는 방법을 알려주는 블로그도 있었다. 내가 찾은 블로그는 다음과 같았다.
https://dalgoodori.tistory.com/43
[Flutter] Google Login ① - 준비
소셜 로그인 구현 두번째는 구글 로그인 입니다. 파이어베이스를 사용하면 훨씬 간단하지만 파이어베이스 없이 구현해보겠습니다. 먼저 프로젝트를 만들기 위해 Google Cloud Platform 에서 계정의
dalgoodori.tistory.com
완전 최신자료는 아니지만 아직 유효한 방법이다. GCP에서 OAuth를 그냥 생성하면 되는데 여기서 SHA1 키가 필요하다. 그 키는 keytool 명령어를 통해서 생성하여 적어주면 된다. keytool로 생성된 서명 키는 프로젝트 뿐만 아니라 JDK 전역에서 관리되는 키와 키스토어를 관리하므로 나중에 배포하게 된다면 그 호스팅 서버에도 작성되어 있어야 한다. 앱 같은 경우 구글 콘솔에서 관리해주므로 키 값만 잘 관리해주면 된다.
다만 이 블로그 설명에서 생성된 OAuth의 json을 google-services.json에 적으라고 하는데 우리는 client ID만 가져와서 파이어베이스에 적어주면 된다.
파이어베이스 Authentication
앞서 client ID를 가져와서 파베의 Authentication에 등록해야한다고 했다. 어디다가 하느냐? 로그인 제공업체에서 Google을 만들어준다.
만들어준 설정창에 들어가게되면
이 빨간 부분에 ID 입력하고 나머지는 기본으로 생성되는 거 적용하면 된다. (이메일이랑 공개용 이름)
AuthService
일단 파이어베이스 기본설정은 따로 적지 않았는데 반드시 해주어야한다. 파이어베이스 설정과정을 완료했다는 가정하에 이제 마지막으로 AuthService 코드를 만들어주면 된다.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
class AuthService {
static final FirebaseAuth _auth = FirebaseAuth.instance;
static final GoogleSignIn _googleSignIn = GoogleSignIn();
// Google Sign-In 및 ID 토큰 서버 전송
static Future<void> signInWithGoogleAndSendToken() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) return;
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
final User? user = userCredential.user;
if (user != null) {
String? idToken = await user.getIdToken();
// ID 토큰을 서버로 전송 및 로컬에 저장
// await _sendTokenToServer(idToken);
await _saveTokenToLocal(idToken);
}
} catch (e) {
print("Error during Google Sign-In: $e");
}
}
// ID 토큰을 서버로 전송하는 메서드
static Future<void> _sendTokenToServer(String? idToken) async {
if (idToken != null) {
final response = await http.post(
Uri.parse('https://your-server.com/authenticate'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'idToken': idToken}),
);
if (response.statusCode == 200) {
print('Token sent to server successfully');
} else {
print('Failed to send token to server: ${response.statusCode}');
}
}
}
// ID 토큰을 로컬에 저장하는 메서드
static Future<void> _saveTokenToLocal(String? idToken) async {
if (idToken != null) {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('id_token', idToken);
print('ID Token saved to local storage.');
}
}
// 로컬에서 ID 토큰을 불러오는 메서드
static Future<String?> getTokenFromLocal() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('id_token');
}
// 로그아웃 메서드
static Future<void> signOut() async {
await _auth.signOut();
await _googleSignIn.signOut();
// 로컬에 저장된 토큰 삭제
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.remove('id_token');
print('ID Token removed from local storage.');
}
// 구글 인증이 완료되었는지 확인하는 메서드
static Future<bool> isUserSignedIn() async {
final user = _auth.currentUser;
// 사용자가 로그인되어 있고, 구글 계정을 통해 인증된 경우 true 반환
if (user != null) {
final idTokenResult = await user.getIdTokenResult();
return idTokenResult.claims!.containsKey('firebase');
}
// 로그인되지 않았거나 구글 계정 인증이 아닌 경우 false 반환
return false;
}
// 로그인된 사용자의 UID를 반환하는 메서드
static String? getUserId() {
final User? user = _auth.currentUser;
return user?.uid; // 사용자가 로그인되어 있으면 UID 반환, 아니면 null 반환
}
}
signInWithGoogleAndSendToken
메소드를 살펴보자.
_googleSignIn.signIn()
을 통해 열리는 UI는 OAuth 서비스를 만들어 놓지 않아도 자동으로 구글 계정 연동 UI가 나타난다. 하지만 계정을 선택하면 유효한 토큰이 발급되지 않으므로 OAuth 설정을 반드시 해주어야한다.
credential
로 만들어진 구글 개인 계정의 토큰을 _auth.signInWithCredential
로 파이어베이스에 전달하면 파이어베이스는 내 OAuth client ID를 알고 있고 내 프로젝트의 SHA1 키가 일치하니 유효성 검증을 하는 거 같다. OAuth의 원리는 나도 잘 모르니 추측이다. 작동이 되서 자세히 알아보진 않았다. 필요할 때 찾아볼 거니 혹시나 이 내용을 보는 사람이 있다면 방금 내용은 거르길...
그다음 idToken
에 토큰을 만들고 로컬에 저장과 서버로 전송한다. 여기서 서버는 DB가 작성된 서버이다. 하지만 아직 서버에 대해서는 사용하지 않을 것이니 토큰 전송만 구현한다.
나머지 메소드는 설명을 생략한다.
'일상 > 개발일지' 카테고리의 다른 글
[Dev] LLM을 활용 단어장 앱 개발일지 010: Tag(Bloc,DB)와 Tag Page 구성 (2) | 2024.09.26 |
---|---|
[Dev] LLM을 활용 단어장 앱 개발일지 008: 단어 데이터 Bloc 처리, SQLite 통신, CRUD 작업 (1) | 2024.09.03 |
[Dev] LLM을 활용 단어장 앱 개발일지 007: 핵심 - 파인튜닝, 응답 데이터 전처리, 단어 추가 UI 구성 (0) | 2024.08.27 |
[Dev] LLM을 활용 단어장 앱 개발일지 006: 단어장 UI 변경 (0) | 2024.08.27 |
[Dev] LLM을 활용 단어장 앱 개발일지 005: CRUD - 단어 DB 읽어서 표시하기 (0) | 2024.08.20 |