[Social Login] 토큰 데이터 저장 공간을 Keychain으로 바꿔보자

2022. 1. 16. 15:54Framework, Library

728x90

본격 떡밥 회수 프로젝트(?)

 

[Social Login] Access Token과 Refresh Token, 그리고 Auto Login까지

이번 나다 NADA 어플 릴리즈를 준비하면서 가장 많이 공부한 부분이 "로그인"과 관련된 부분일 거다. 처음 아요끼리 담당 기능, 파트를 나눌 때, 내가 로그인을 맡겠다고 (겁 없이) 지원했었는데...

mini-min-dev.tistory.com


지난번 글에서 로그인 부분을 정리하면서, 토큰과 자동 로그인, UserDefaults까지 모든 내용을 다 공부했었지만,
유일하게 하나 다른 블로그 링크를 첨부해둔 채로 남겨둔 부분이 있었다.

그 부분은 바로 iOS에서 보안과 관련된 정보를 다루는 방법인 "Keychain" 내용 부분이었다.

첫 릴리즈 버전에서는 keychain 대신, 익숙한 UserDefaults 방법을 사용해서 토큰과 아이디 등 개인정보를 저장해두었지만,
결국 정석은 이렇게 하면 안 되기 때문에!

이번 기회에 Keychain을 사용하는 방식으로 리팩토링도 할 겸, 동시에 블로그에 공부 흔적을 남겨보려고 한다.

 

1. Keychain이 뭐라고?


항상 시작은 Apple 개발자 공식문서로 시작을 해보자.

Keychain을 간단하게 한 줄로 말하자면, Apple에서 제공하는 암호화된 데이터 저장 공간(encrypted database)이다.

이 암호화된 저장 공간에는 주로, 보안이 중요한 개인정보를 담아두게 된다.
아래 사진에 보이는 것처럼 비밀번호 이외에도 금융 정보, 인증서, 그리고 지난번 글에서 배운 토큰값 등을 저장할 때는 모두 이 공간을 활용하는 것을 원칙으로 한다.

Keychain이 저장해둘 수 있는 정보 예시

UserDefaults를 기준으로 생각해 보면, 
데이터가 Key-Value 형태로 구성되어 있어 Key값으로 자신이 원하는 데이터를 저장, 삭제, 수정할 수 있었는데, Keychain도 큰 틀에서는 이와 비슷하다.

아래 그림을 보면서 이해해보자.

Data와 Attribites를 모두 애플에서 제공하는 Keychain services API를 이용하여 Item으로 패키징하고, 이를 Keychain이라는 공간에 저장해두는 형태이다.
이때, Data는 실제 민감한 개인정보 데이터에 해당, 암호화(encryption)하여 패키징을 하게 되고,
이 암호화된 데이터에 접근하기 위한 Attributes도 함께 패키징 된다.

그럼, 실제 데이터에 접근할 때 Attribites 데이터로 가져올 때는 자연스럽게 암호화된 데이터 해독 방식으로 할 수 있도록 Apple이 기본 제공한다.

Data, Attributes, 암호화(Encryption) 과정의 이해 그림

추가로, 알아둬야 할 Keychain의 기본 특징 몇가지가 있다.

  1. Keychain은 앱을 삭제하더라도, 정보가 삭제되지 않음
  2. Keychain에는 잠금 기능이 존재, 잠긴 상태에서는 아이템 값의 접근, 복호화 등 아무런 작업이 불가능함
  3. 같은 개발자가 구현한 앱이 여러개라면, 그 앱 사이의 Keychain 정보는 공유가 가능

특히, 1번 같은 경우에는 앱 삭제 시 정보가 같이 지워졌던 UserDefaults와는 구분되는 점이기 때문에, 리팩토링 시 같이 고려해야 할 점일거다. -> 추후, 팀원 전체와 이야기 해보고 업데이트할 부분

정보가 지워지지 않는 이유는 Keychain의 위치가 Sandbox라는 곳에 있기 때문이라는ㄷ...이거는 나중에 또 보기로 하자..어렵ㄷ 

 

2. Keychain item class와 Attributes에 대해 더 공부해보자


위에서 살짝 Data, Attributes, Item에 대해서 언급했었는데, 그 부분을 조금 더 자세하게 공부하고 가보자.

Apple에서는 Keychain에 저장할 데이터의 종류에 따라서 Item Class를 구분해두었다.
그리고, 이 Item Class에 따라서 설정할 수 있는 attributes(속성)이 달라지게 된다.

아래에서 데이터의 종류에 따라 구현할 수 있는 Item Class 이름에 공식 문서 링크를 연결해두었다. 직접 들어가서, 사용할 수 있는 각각의 속성은 무엇이 있는지 확인해보도록 하자.

그럼 이제 본격적으로, 코드 리팩토링을 하러 본격적으로 떠나보자 ^__^

 

3. Keychain에서 사용하는 공통 함수를 코드로 구현해보자!


우선, Keychain에서 사용되는 함수들은 프로젝트 내에서 재사용을 위해,
KeyChain이라는 이름의 별도 클래스를 만든 후, 이 안에 코드를 작성했다.

항목을 추가하는 Create, 키체인 항목을 반환하는 Read, 내용을 지워주는 Delete까지 순서대로 살펴보자.

Create

class func create(key: String, token: String) {
    let query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: key,   // 저장할 Account
        kSecValueData: token.data(using: .utf8, allowLossyConversion: false) as Any   // 저장할 Token
    ]
    SecItemDelete(query)    // Keychain은 Key값에 중복이 생기면, 저장할 수 없기 때문에 먼저 Delete해줌

    let status = SecItemAdd(query, nil)
    assert(status == noErr, "failed to save Token")
}

 

Read

class func read(key: String) -> String? {
    let query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: key,
        kSecReturnData: kCFBooleanTrue as Any,  // CFData 타입으로 불러오라는 의미
        kSecMatchLimit: kSecMatchLimitOne       // 중복되는 경우, 하나의 값만 불러오라는 의미
    ]
    
    // READ
    var dataTypeRef: AnyObject?
    let status = SecItemCopyMatching(query, &dataTypeRef)
    
    if status == errSecSuccess {
        let retrievedData = dataTypeRef as! Data
        let value = String(data: retrievedData, encoding: String.Encoding.utf8)
        return value
    } else {
        print("failed to loading, status code = \(status)")
        return nil
    }
}

Delete

class func delete(key: String) {
    let query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: key
    ]
    let status = SecItemDelete(query)
    assert(status == noErr, "failed to delete the value, status code = \(status)")
}

 

전체 코드

import Foundation
import Security

class KeyChain {
    // Create
    class func create(key: String, token: String) {
        let query: NSDictionary = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,   // 저장할 Account
            kSecValueData: token.data(using: .utf8, allowLossyConversion: false) as Any   // 저장할 Token
        ]
        SecItemDelete(query)    // Keychain은 Key값에 중복이 생기면, 저장할 수 없기 때문에 먼저 Delete해줌

        let status = SecItemAdd(query, nil)
        assert(status == noErr, "failed to save Token")
    }
    
    // Read
    class func read(key: String) -> String? {
        let query: NSDictionary = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key,
            kSecReturnData: kCFBooleanTrue as Any,  // CFData 타입으로 불러오라는 의미
            kSecMatchLimit: kSecMatchLimitOne       // 중복되는 경우, 하나의 값만 불러오라는 의미
        ]
        
        var dataTypeRef: AnyObject?
        let status = SecItemCopyMatching(query, &dataTypeRef)
        
        if status == errSecSuccess {
            if let retrievedData: Data = dataTypeRef as? Data {
                let value = String(data: retrievedData, encoding: String.Encoding.utf8)
                return value
            } else { return nil }
        } else {
            print("failed to loading, status code = \(status)")
            return nil
        }
    }
    
    // Delete
    class func delete(key: String) {
        let query: NSDictionary = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrAccount: key
        ]
        let status = SecItemDelete(query)
        assert(status == noErr, "failed to delete the value, status code = \(status)")
    }
}

 

4. UserDefaults 부분 Keychain으로 리팩토링하기


이제 위에서 구현한 공통 클래스를 바탕으로, 기존 Access Token과 Refresh Token 부분 코드를 리팩토링 해줄 차례이다.
하나씩 아래 사진으로 살펴보자.

1) 로그인 시, 토큰 저장 부분 (UserDefaults.standard.set -> KeyChain.create 변경)


2) 로그아웃 시, 토큰 삭제 부분 (UserDefaults.standard.removeObject -> KeyChain.delete 변경)


3) 처음 splash 부분에서 acToken값 불러와 체크하는 부분 변경

 

💡Reference


 

[iOS] Keychain 키체인 사용법

사용자를 대신하여 작은 데이터들을 안전하게 저장하는 저장소keychain service - Apple 공식 문서애플 문서를 보면, 사용자들은 종종 안전하게 보관해야 할 데이터들이 있다고 한다. 예를 들어, 대부

velog.io

 

[iOS] KeyChain이란~? Swift코드를 통해 살펴보기

iOS 앱 개발 프로젝트를 할 때 민감한 정보를 어디에 저장해야할지 고민이됐다. UserDefaults보다 보안이 뛰어난 키체인을 사용해보자! UserDefaults UserDefaults 에도 데이터를 쉽게 저장할 수 있지만, 단

adora-y.tistory.com

 

[iOS - swift] 7. 서버 - Key Chain 핸들링

Key Chain에 대한 기본 개념은 아래 링크 참고 [iOS - swift] 6. 서버 - OAuth, Key Chain, 로그인 관리 토큰 1. OAuth란? - Third-party application의 인증 권환부여 및 관리를 위해서 사용 (특정 쇼핑몰 회원가..

ios-development.tistory.com

 

키체인 간단 사용법

키체인에 대해 알아보고, 구현해보는 글입니다

daeun28.github.io

728x90