방명록
- Android DataStore로 토큰 처리하기 [Kotlin]2023년 11월 14일 17시 08분 26초에 업로드 된 글입니다.작성자: Moonsu99
토큰 저장 방법 3가지 비교
SharedPreferences
쉽게 설명하자면 가벼운 데이터를 Key-Value로 저장하기 위한 매커니즘이라고 생각하면 된다.
앱을 종료해도 데이터가 계속 유지되서 앱 설정이나 토큰같은 간단한 정보를 저장하는데 좋음.
// SharedPreferences 객체 가져오기 SharedPreferences sharedPreferences = getSharedPreferences("my_prefs", Context.MODE_PRIVATE); // 데이터 저장 SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString("username", "JohnDoe"); editor.apply(); // 데이터 검색 String username = sharedPreferences.getString("username", "DefaultUsername");
근데 요즘 왜 토큰을 저장하는데 잘 사용하지 않는 이유?
암호화가 안됨 그래서 루팅된 기기에서 접근이 된다고 함.
KeyStore
암호화 키를 보다 안전하게 관리할 수 있다. 키는 하드웨어 보안 모듈에 의해 보호되며, 앱 외부에서는 접근할 수 없어서 보안에 좋다.
fun createKey(alias: String): SecretKey { val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "TestKeyStore") keyGenerator.init( KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build() ) return keyGenerator.generateKey() } // 키 생성 예시 val secretKey = createKey("MyKeyAlias")
사용하긴 하는데 왜? dataStore보다 사용하지 않는가?
구현이 복잡하며, 안드로이드 버전과 호환성을 고려를 해야 하기 때문에 사용하기에 어려움이 있음.
DataStore
SharedPreferences의 대안으로, Coroutine과 Flow를 사용하여 비동기적이고 반응형의 데이터 저장 및 관리를 제공함.
데이터 일관성과 트랜잭션이 보장되며, 암호화와 같은 추가 보안 조치를 쉽게 구현할 수 있다.
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "token_preferences") class TokenRepository(private val context: Context) { private val TOKEN_KEY = stringPreferencesKey("jwt_token") val token: Flow<String?> get() = context.dataStore.data.map { preferences -> preferences[TOKEN_KEY] } suspend fun saveToken(token: String) { context.dataStore.edit { preferences -> preferences[TOKEN_KEY] = token } } } // 사용 예시 val tokenRepository = TokenRepository(context) // 토큰 저장 tokenRepository.saveToken("your_jwt_token") // 토큰 가져오기 val tokenFlow = tokenRepository.token
단점으로는 비교적 최근에 나온 API이기 때문에 기존 SharedPreferences 사용자들에게는 학습 곡선이 있을 수 있다고 함.
결론적으로 왜? dataStore가 많이 사용 되는 것 일까?
- DataStore는 데이터의 비동기적 처리와 보다 효율적인 데이터 관리를 가능하게 하는 코루틴과 플로우를 지원하기 때문에.
- 보안적인 측면에서 DataStore는 SharedPreferences보다 강력한 옵션을 제공해서.
- DataStore는 데이터 일관성과 트랜잭션 관리에 있어서 SharedPreferences보다 더 나은 해결책을 제공해서.
파일 기반 토큰 인증시스템 흐름
dataStore, Interceptor, Authenticator을 이용한 토큰 인증-재발급 구현
1. 초기 설정 및 Retrofit 인스턴스 생성 (RetrofitInstance.kt)
- 앱 시작 시 RetrofitInstance 객체의 init 메소드를 호출.
- 초기화 하지 않으면 “lateinit property has not been initialized” 오류가 발생함.
- **UserTokenDataStore**를 사용하여 TokenManager 인스턴스를 초기화.
- **OkHttpClient**를 생성하고 여기에 AuthInterceptor 및 Log를 확인 할 수 있게**loggingInterceptor**을 추가.
- Retrofit 인스턴스를 생성하고, RenewTokenApiService 및 기타 필요한 API 서비스 인터페이스를 초기화.
- **AuthAuthenticator**를 **OkHttpClient**에 설정하여 토큰 만료 시 자동 재발급 로직을 활성화.
- **ApiServiceTest**와 같은 추가적인 서비스 인스턴스를 생성.
2-1. 로그인 및 토큰 저장 (AuthInterceptor.kt)
- 사용자가 로그인을 시도할 때, **AuthInterceptor**는 로그인 요청을 감지하고 Authorization에 AccessToken 없이 요청을 진행.
- 또한 로그인이 성공하면 서버로부터 받은 set-cookie Header에서 RefreshToken을 추출하고 **TokenManager**를 사용하여 로컬 저장소에 저장.
2-2. API 요청 및 액세스 토큰 적용 (AuthInterceptor.kt)
- API 요청이 발생하면 **AuthInterceptor**가 해당 요청을 가로채 accessToken을 요청 헤더에 추가.
- 토큰 재발급 요청(/member/renew-access-token)은 예외 처리하여 Authorization 헤더를 추가하지 않게 하기.
3. 토큰 만료 및 자동 재발급 (AuthAuthenticator.kt)
- 서버로부터 401 Unauthorized 응답을 받으면 **AuthAuthenticator**가 호출되게 설정.
- **AuthAuthenticator**는 **TokenManager**를 사용하여 refreshToken을 가져온 후, **RenewAccessTokenApiService**를 통해 새 accessToken을 요청.
- 새 토큰을 받으면 **TokenManager**를 사용하여 저장하고, 해당 토큰으로 요청을 재구성하여 재시도.Authenticator 과정 상세보기
- 1. 인증 실패 감지
- **AuthAuthenticator**는 네트워크 요청이 서버로부터 401 Unauthorized 응답을 받았을 때 자동으로 호출함.
- 2. 리프레시 토큰 검색
- **AuthAuthenticator**는 **TokenManager**의 getRefreshTokenBlocking() 메서드를 사용하여 저장된 refreshToken을 동기적으로 검색.
- 만약 refreshToken이 없거나 가져오기에 실패하면, 토큰 재발급 과정은 중단되고, null이 반환.
- 3. 새 액세스 토큰 요청
- refreshToken이 있는 경우, **AuthAuthenticator**는 **RenewAccessTokenApiService**의 renewAccessTokenSync() 메서드를 호출하여 새 accessToken을 요청.
- 이 때, refreshToken은 HTTP 요청의 Cookie 헤더를 통해 서버에 전달.
interface RenewAccessTokenApiService { @POST("/test") fun renewAccessTokenSync( @Header("Cookie") refreshToken: String ): Call<RenewAccessTokenResponse> }
- 4. 응답 처리 및 토큰 저장
- 서버로부터 새 accessToken, refreshToken을 포함한 응답을 받으면, **AuthAuthenticator**는 이를 **TokenManager**를 통해 저장.
- **TokenManager**의 saveAccessTokenBlocking() 메서드를 사용하여 token을 저장.
- 새 토큰을 저장한 후, **AuthAuthenticator**는 원래 실패했던 네트워크 요청을 새 accessToken으로 업데이트하여 재구성.
- 1. 인증 실패 감지
'Android [ Java, Kotlin ]' 카테고리의 다른 글
안드로이드 디자인 패턴(MVC,MVP,MVVM) 정리 (0) 2023.11.16 Android Firebase FCM 정리 (0) 2023.11.15 Appium을 이용해 안드로이드 테스트 자동화 환경 구축하기 (0) 2023.10.20 안드로이드 Retrofit2 통신 시 HTTPS가 아닌 HTTP로 설정하기 (0) 2023.10.19 안드로이드 프로그래밍 - 커스텀 프로그래스바(Progressbar) (0) 2023.10.16 다음글이 없습니다.이전글이 없습니다.댓글