일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 플레이스토어
- 로그인
- 컴포즈
- NavHost
- sharedFlow
- ListAdapter
- 회원가입
- Flow
- Kotlin
- Jetpack
- NavController
- coroutine
- 클린아키텍처
- Android
- 파이어베이스
- 뷰
- Compose
- XML
- 코틀린
- 커스텀뷰
- 코딩테스트
- cleanarchitecture
- Authentication
- DiffUtil
- UiState
- Build variants
- 안드로이드
- 리사이클러뷰
- 알고리즘
- MVVM
- Today
- Total
Grusie 안드로이드 개발 기술 블로그
[Android] 파이어베이스 이메일 회원가입 - 3 (인증 및 자동 로그인) 본문
이전 게시글까지 이메일 인증을 구현하였고, 이제는 온전한 회원가입을 진행하기 위해, 이메일과 비밀번호로 진짜 회원가입을 진행하고 자동로그인을 처리하는 것까지 진행해보자.
이 전 게시글들을 못 본 사람들은 보고 오도록 하자.
2024.03.15 - [안드로이드 개발] - [Android] 파이어베이스 이메일 회원가입 - 1 (이메일 링크 인증)
[Android] 파이어베이스 이메일 회원가입 - 1 (이메일 링크 인증)
파이어베이스 설정을 완료 하였으니, 이제 회원가입과 로그인을 다뤄야 할 차례이다. 제일 먼저 이메일로 인증을 하는 방법을 먼저 알아보자. 만약 파이어베이스 설정을 아직 완료하지 않았다
grusie.tistory.com
2024.03.15 - [안드로이드 개발] - [Android] 파이어베이스 이메일 회원가입 - 2 (이메일 인증)
[Android] 파이어베이스 이메일 회원가입 - 2 (이메일 인증)
이전에 잘못 된 판단으로 인한 플로우 변경을 시도한다. 이메일 인증을 지원하는 것 같기에 그걸 사용해 보려고 한다. 이메일 인증을 요청하기 이전 내용들을 동일하기에 이전 글과 이어서 작성
grusie.tistory.com
우선 이메일 인증을 하거나, 이메일 보내기만 했을 경우에도 임의로 유저정보를 만들어뒀기 때문에, 삭제를 해주어야 하지만, 라이프사이클을 가져오는 것만으로 계정을 삭제 해버리면, 오류가 날 수 있으니, 차라리 임의로 생성하고, 사용자가 강제종료 했다면, 다음번에 들어왔을 때 비밀번호 변경을 시도하도록 해야 할 것 같다.
본격적으로 회원가입 로직을 만들어보자
SignUpViewModel
fun signUpEmail() {
when {
_idText.value.isEmpty() || _pwText.value.isEmpty() -> { //필수값이 입력되지 않았을 때
setSignUpUiState(SignUpUiState.Alert(Constant.ERROR_CODE_EMPTY))
}
_verifyChecked.value != true -> { //이메일 인증이 되지 않았을 때
setSignUpUiState(SignUpUiState.Alert(Constant.ERROR_EMAIL_UNVERIFIED))
}
!isValidPassword() -> { //비밀번호 패턴이 맞지 않을 때
setSignUpUiState(SignUpUiState.Alert(Constant.ERROR_PASSWORD_FORMAT))
}
else -> { //회원가입 로직
viewModelScope.launch {
try {
authUseCases.emailSignUpUseCase(email = _idText.value, password = _pwText.value)
setSignUpUiState(SignUpUiState.SuccessSignUp)
}catch (e:Exception){
setSignUpUiState(SignUpUiState.Error(e))
}
}
}
}
}
각 패턴에 맞는 에러 처리를 진행하고, 전부 해당하지 않을 시 회원가입 로직을 수행한다.
EmailSignUpUseCase
suspend operator fun invoke(email: String, password: String) : LocalAuth{
try {
auth.currentUser?.delete()?.await() //이전에 임의로 생성한 계정을 삭제
auth.createUserWithEmailAndPassword(email, password).await()
val encryptedPassword = Utils.encryptData(password.toByteArray(Charsets.UTF_8), BuildConfig.PASSWORD_AES_KEY)
return LocalAuth(uid = auth.currentUser!!.uid, id = email, pw = encryptedPassword)
} catch (e: Exception) {
Log.e("confirm signUp Error : ", "${e.message}")
throw e
}
}
이메일 비밀번호로 회원가입 후, 로그인 된 비밀번호를 암호화하여, Local 데이터베이스에 저장하기 위해 LocalAuth를 리턴 -> 추후 복호화하여 사용 예정
사실 로그인 했을 때 받아오는 idToken을 가지고 JWT 등 CustomToken으로 변환하여 토큰을 활용하여 로그인 하는 게 베스트겠지만, 필자는 서버가 없으니 아이디 비밀번호를 저장하여 사용하고자 한다.
자동로그인 처리
회원가입 한 아이디 로그인을 들어올 때 마다 자동으로 처리하기 위해서, RoomDB에 아이디, 비밀번호를 저장해둘 예정이다.
회원 본인 정보만 저장 하기에 RoomDB는 너무 사이즈가 커진다고 생각하여, SharedPreference에 저장하려고한다.
LocalAuthUseCases
data class LocalAuthUseCases(
val createLocalAuthUseCase: CreateLocalAuthUseCase,
val getLocalAuthUseCase: GetLocalAuthUseCase,
val updateLocalAuthUseCase: UpdateLocalAuthUseCase,
val deleteUserInfoUseCase: DeleteLocalAuthUseCase
)
로컬DB 저장을 담당할 UseCase들
LocalAuth
data class LocalAuth(
val uid: String,
val id: String,
val pw: String
)
저장할 데이터 클래스 모델
LocalAuthRepository
interface LocalAuthRepository {
suspend fun getLocalAuth(): Flow<LocalAuth>
suspend fun deleteLocalAuth()
suspend fun createLocalAuth(localAuth: LocalAuth)
suspend fun updateLocalAuth(localAuth: LocalAuth)
}
실제 데이터를 저장할 Repository
LocalAuthRepositoryImpl
class LocalAuthRepositoryImpl @Inject constructor(
private val localAuthSource: LocalAuthSource,
) : LocalAuthRepository {
override suspend fun getLocalAuth(): Flow<LocalAuth> {
return localAuthSource.getLocalAuth().map {
it.toLocalAuth()
}
}
override suspend fun deleteLocalAuth() {
localAuthSource.deleteLocalAuth()
}
override suspend fun createLocalAuth(localAuth: LocalAuth) {
localAuthSource.createLocalAuth(localAuth)
}
override suspend fun updateLocalAuth(localAuth: LocalAuth) {
localAuthSource.updateLocalAuth(localAuth)
}
}
LocalAuthSource
interface LocalAuthSource {
suspend fun createLocalAuth(localAuth: LocalAuth)
suspend fun getLocalAuth(): Flow<LocalAuthData>
suspend fun updateLocalAuth(localAuth: LocalAuth)
suspend fun deleteLocalAuth()
}
class LocalAuthSourceImpl(private val sharedPreferences: SharedPreferences) : LocalAuthSource {
companion object {
private const val KEY_UID = "uid"
private const val KEY_ID = "id"
private const val KEY_PW = "pw"
}
override suspend fun createLocalAuth(localAuth: LocalAuth) {
sharedPreferences.edit().apply {
putString(KEY_UID, localAuth.uid)
putString(KEY_ID, localAuth.id)
putString(KEY_PW, localAuth.pw)
apply()
}
}
override suspend fun getLocalAuth(): Flow<LocalAuthData> {
return flow {
val uid = sharedPreferences.getString(KEY_UID, null)
val id = sharedPreferences.getString(KEY_ID, null)
val pw = sharedPreferences.getString(KEY_PW, null)
if (!uid.isNullOrEmpty() && !id.isNullOrEmpty() && !pw.isNullOrEmpty()) {
emit(LocalAuthData(uid, id, pw))
}
}
}
override suspend fun updateLocalAuth(localAuth: LocalAuth) {
sharedPreferences.edit().apply {
putString(KEY_ID, localAuth.id)
putString(KEY_PW, localAuth.pw)
apply()
}
}
override suspend fun deleteLocalAuth() {
sharedPreferences.edit().apply {
remove(KEY_UID)
remove(KEY_ID)
remove(KEY_PW)
apply()
}
}
}
LocalAuth의 실제 CRUD 로직 처리
모든 파일들의 각각의 의존성을 주입해주고, viewmodel -> useCase -> repository -> source 순으로 처리하도록 구현
SharedPreference에 uid, id, pw(암호화된)를 저장
이제 필요한 기능들은 전부 만들어뒀으니 조합해서 사용하면 된다.
LoginEmailUseCase
class LoginEmailUseCase(
private val auth: FirebaseAuth
) {
suspend operator fun invoke(id: String, pw: String) {
try {
auth.signInWithEmailAndPassword(id, pw).await()
}catch (e:Exception){
Log.e("confirm loginEmail Error : ", "${e.message}")
throw e
}
}
suspend operator fun invoke(localAuth : LocalAuth) {
try {
val decryptedPassword = Utils.decryptData(localAuth.pw, BuildConfig.PASSWORD_AES_KEY)
auth.signInWithEmailAndPassword(localAuth.id, decryptedPassword).await()
}catch (e:Exception){
Log.e("confirm loginEmail Error : ", "${e.message}")
throw e
}
}
}
로그인을 담당하는 UseCase, 자동로그인일시에는 암호화가 되어있기에, 복호화를 하여 로그인 하고, 일반 로그인의 경우 입력한 아이디와 패스워드로 로그인을 처리한다.
SplashViewModel
private fun authEmailLogIn() {
viewModelScope.launch {
try {
localAuthUseCases.getLocalAuthUseCase().collectLatest { localAuth ->
emailAuthUseCases.loginEmailUseCase(localAuth)
}
_splashUiState.emit(SplashUiState.SuccessLogin)
}catch (e:Exception) {
Log.e("confirm error : ", "${e.message}")
_splashUiState.emit(SplashUiState.Error(e))
}
}
}
자동 로그인을 진행 할 SplashViewModel이다. localAuthUseCases를 통해, 로컬디비에 저장된 아이디/비밀번호가 있다면, 로그인을 시도한다.
후기
자동로그인을 구현하는 과정에서, 생각해야 할 게 너무 많았다.
SharedPreferences를 활용해서 유저 정보를 저장하는 게, 클린아키텍처 관점에서 꽤 작업이 큰 과정이었으며, 마음같아선 accessToken을 발급받아 저장하고 싶으나, customToken을 발급하는 것은 백엔드에서 처리하는 것이 좋다고 하여, 아이디 비밀번호를 저장하는 형태로 구현을 하였고, 비밀번호는 암호화처리를 해뒀기에 괜찮을 것이라 생각한다.
다음엔, 회원가입 시 유저 정보를 파이어스토어에 저장하여 그 정보를 불러오는 것까지 진행 할 예정이다.
'안드로이드 개발 > 파이어베이스' 카테고리의 다른 글
[Android] 파이어스토어를 활용해 유저 정보 가져오기 (0) | 2024.04.01 |
---|---|
[Android] 파이어베이스 이메일 회원가입 - 2 (이메일 인증, 딥링크 회고) (0) | 2024.03.15 |
[Android] 파이어베이스 이메일 회원가입 - 1 (이메일 링크 인증) (0) | 2024.03.15 |
[Android] 안드로이드 프로젝트에 Firebase 추가하기 (0) | 2024.03.13 |