관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] KeychainAccess프레임워크로 키체인(keychain)관리하기 본문

iOS 응용 (swift)

[iOS - swift] KeychainAccess프레임워크로 키체인(keychain)관리하기

jake-kim 2020. 11. 15. 17:04

* 키체인을 사용해서 데이터를 저장해도, 디바이스에서 보안 툴을 가지고 키체인에 저장된 문자열들을 모두 알 수 있기 때문에 암호화 하여 저장하고, 복호화하여 읽도록 필요: ios-development.tistory.com/manage/posts/

Kehchain pod 종속성

pod 'KeychainAccess', '4.1.0'

Clean Architecture에 의해서, 앱을 Domain과 App영역으로 나눈다면, Keychain의 protocol(Domain영역)과 구현(App영역)을 분리

App영역에서 사용할 때는 Domain영역의  값을, 화면전환 할 때 주입해주는 구조로 구성하여, 추후에 테스트하기 쉽게 구현하는 것이 중요

 

ex) MVVM구조를 사용한다면, ViewModel에 Dependencies라는 struct에 정의

    struct Dependencies {
        let keychain: KeyValueStore // KeyValueStore는 Domain의 protocol
    }

위 내용을 해당 화면으로 이동할 때, KeyValueStore의 구현체를 주입해주는 형태

구조의 이점

  • 유지보수에 용이(결합도와 응집도): 키체인 로직에 대해서 수정하고 싶을 때, 구현체만 바꾸어주면 사용하는 입장에서 하나도 수정할 것이 없고 그대로 사용해도 되는 이점)
  • 테스트에 용이: 구현체를 따로 만들어서 여러가지 경우를 주입시켜가며 테스트에 이점

앱 구조 설정

앱 구조 및 cocoa pod 여러 프로젝트 의존성 관리 방법은 여기 참고

  • 메인 앱: MainProj.xcodeproj
  • 도메인: Domain.xcodeproj
  • podfile: KeychainAccess 의존성을 MyApp프로젝트에 주입 (아래 파일 입력 후 pod install)
platform :ios, '11.0'
use_frameworks!
inhibit_all_warnings!

workspace 'MyApp'
project 'MyApp/MyApp.project'
project 'Domain/Domain.project'

def data_pods
  pod 'KeychainAccess'
end

target 'MyApp' do
  project 'MyApp/MyApp.project'
  data_pods
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0'
      config.build_settings['SWIFT_VERSION'] = '5.1'
    end
  end
end


구조 세팅 된 화면

KeyChain에 사용될 키 정의

Domain/Constants.swift

//
//  Constants.swift
//  Domain
//
//  Created by 김종권 on 2020/11/15.
//

import Foundation

struct Constants {

    struct KeyChainKey {
        static let name     = "name"
        static let email    = "eamil"
    }

}
  • Domain에 keychain관련 기능 프로토콜 추가 "KeyValueStore"
    (바뀌지 않을 공통 로직은 extension으로 구현)
//
//  KeyValueStore.swift
//  Domain
//
//  Created by 김종권 on 2020/12/16.
//

import Foundation

public protocol KeyValueStore {
    func save(key: String, value: String)
    func get(key: String) -> String?
    func delete(key: String)
    func removeAll()
}

public extension KeyValueStore {

    func getName() -> String {
        return get(key: Constants.KeyChainKey.name) ?? ""
    }

    func getEmail() -> String {
        return get(key: Constants.KeyChainKey.email) ?? ""
    }

    func saveName(_ name: String) {
        return save(key: Constants.KeyChainKey.name, value: name)
    }

    func saveEmail(_ email: String) {
        return save(key: Constants.KeyChainKey.email, value: email)
    }

    func deleteUserInfo() {
        delete(key: Constants.KeyChainKey.name)
        delete(key: Constants.KeyChainKey.email)
    }
}
  • MyApp에 Keychain관련 싱글턴으로 구현체 추가 "KeychainService"
//
//  KeychainService.swift
//  MyApp
//
//  Created by 김종권 on 2020/11/15.
//

import Foundation
import Domain
import KeychainAccess

public class KeychainService: KeyValueStore {

    public static let shared = KeychainService()
    private init() {}

    let lockCredentials = Keychain()

    public func save(key: String, value: String) {
        do {
            try lockCredentials.set(value, key: key)
        } catch {
            print(error.localizedDescription)
        }
    }

    public func get(key: String) -> String? {
        do {
            guard let key = try lockCredentials.get(key) else {
                return nil
            }
            return key
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }

    public func delete(key: String) {
        do {
            try lockCredentials.remove(key)
        } catch {
            print(error.localizedDescription)
        }
    }

    public func removeAll() {
        do {
            try lockCredentials.removeAll()
        } catch {
            print(error.localizedDescription)
        }
    }


}

 

  • 주입하는 형태로 사용: KeychainService를 갈아 엎더라도, 결국에 Domain에 정의한 KeyValueStore을 쓰고 있으므로 유지보수에 용이
    사용하는 쪽은 (변하지 않는)KeyValueStore 타입을 가지고 있고, 주입시키는 쪽은 (변하는)KeychainService를 사용
//
//  ViewController.swift
//  MyApp
//
//  Created by 김종권 on 2020/11/15.
//

import UIKit
import Domain

class ViewController: UIViewController {

	let vm = ViewModel

    override func viewDidLoad() {
        super.viewDidLoad()
        
		vm = ViewModel(keychain: KeychainService.share)        
    }
}


class ViewModel {
	let keychain: KeyValueStore
    
    init(keychain: Keychain) {
		self.keychain = keychain
	}

	func saveName() {
    	keychain.saveName("Jake")
    }
}

키체인 프레임워크  :  KeychainAccess프레임워크

* 전체 소스코드: github.com/JK0369/keychainSample

 

* 기타) OCP개념을 이용하여 keychain을 사용하는 코드가 있는 글 참고

keychain을 KeychainRepository, KeychainService로 나누고 실제 접근은 KeychainService에서 이루어지도록 하는 코드

Comments