์๋ ํ์ธ์ ์ ์ธ์ ๋๋ค :)
์ต๊ทผ์ ํ๋ก์ ํธ ๊ฐ๋ฐ์ ํ๋ฉฐ ํ ํฐ๊ณผ ๊ฐ์ ์ค์ํ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ข ๋ ์์ ํ๊ฒ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ๊ณ ์ ํค์ฒด์ธ์ ์ด์ฉํด๋ณด์๋๋ฐ์,
ํค์ฒด์ธ์ ๋ํ ๋ด์ฉ๋ ์ ๋ฆฌํ ๊ฒธ, ํค์ฒด์ธ์ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ๊ณต์ ํด๋ณด๋ ค๊ณ ํฉ๋๋ค !
Keychain Service
ํค์ฒด์ธ ์๋น์ค๋ Apple์ด ์ ๊ณตํ๋ ๋ณด์ ํ๋ ์์ํฌ์ ๋๋ค.
๊ณต์๋ฌธ์์ ๋ฐ๋ฅด๋ฉด, ํค์ฒด์ธ ์๋น์ค API๋ ์ฑ์ ํค์ฒด์ธ์ด๋ผ๋ ์ํธํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ์ฌ์ฉ์ ๋ฐ์ดํฐ ๋นํธ๋ฅผ ์ ์ฅํ๋ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํ์ฌ ์ํธ์ ๊ฐ์ด ๋ณด์์ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์ ์ฅํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
์ฆ, ์ํธํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ Keychain์ ์ ๋ณด๋ฅผ ์ ์ฅํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ์ ์ฅํ ์ ์๋๋ก ํฉ๋๋ค.
ํค์ฒด์ธ์ ์ํธ์๋ง ๊ตญํ๋์ง ์์ต๋๋ค. ์ ์ฉ ์นด๋ ์ ๋ณด๋ ๊ฐ๋จํ ๋ฉ๋ชจ์ ๊ฐ์ด ์ฌ์ฉ์๊ฐ ๋ช ์์ ์ผ๋ก ๊ด์ฌ์ ๊ฐ๋ ๋ค๋ฅธ ๋น๋ฐ์ ์ ์ฅํ ์ ์์ผ๋ฉฐ, ์ฌ์ฉ์๊ฐ ํ์๋ก ํ์ง๋ง ์ธ์ํ์ง ๋ชปํ๋ ํญ๋ชฉ์ ์ ์ฅํ ์๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ํธํ ํค ๋ฐ ์ธ์ฆ์๋ฅผ ํตํด ์ฌ์ฉ์๋ ๋ณด์ ํต์ ์ ์ฐธ์ฌํ๊ณ ๋ค๋ฅธ ์ฌ์ฉ์ ๋ฐ ์ฅ์น์ ์ ๋ขฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
Keychain์ ํน์ง
- iOS์์ ์ฑ์ ๋จ์ผ ํค์ฒด์ธ(๋ ผ๋ฆฌ์ ์ผ๋ก iCloud ํค์ฒด์ธ์ ํฌํจํจ)์ ์ก์ธ์คํ ์ ์์ต๋๋ค.
- iOS์์ ํค์ฒด์ธ์ ์์น๋ ์๋๋ฐ์ค ์ธ๋ถ์ด๋ฏ๋ก, ์ฑ์ ์ญ์ ํด๋ ํค์ฒด์ธ์ ์ ์ฅ๋ ์ ๋ณด๋ ์ญ์ ๋์ง ์์ต๋๋ค.
- ํค์ฒด์ธ์ ์ฌ์ฉ์๊ฐ ์ฅ์น์ ์ ๊ธ์ ํด์ ํ๋ฉด ์๋์ผ๋ก ์ ๊ธ ํด์ ๋๊ณ ์ฅ์น๊ฐ ์ ๊ธฐ๋ฉด ์ ๊น๋๋ค.
- ์ฑ์ ์์ ์ ํค์ฒด์ธ ํญ๋ชฉ ๋๋ ์ฑ์ด ์ํ ๊ทธ๋ฃน๊ณผ ๊ณต์ ๋ ํญ๋ชฉ์๋ง ์ก์ธ์คํ ์ ์์ต๋๋ค.
- ํค์ฒด์ธ ์ปจํ ์ด๋ ์์ฒด๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
Keychain Items
ํค์ฒด์ธ์ ์ ์ฅ๋๋ ๊ธฐ๋ฐ ์ ๋ณด๋ item์ผ๋ก ํจํค์งํฉ๋๋ค. ์ฆ, ํค์ฒด์ธ์ ์ ๋ณด๊ฐ ์ ์ฅ๋๋ ๋จ์๋ผ๊ณ ํ ์ ์์ผ๋ฉฐ, ํค์ฒด์ธ์ ํ๋ ์ด์์ Keychain Item์ ๊ฐ์ต๋๋ค. data ์์ฒด์ ํจ๊ป ๊ณต๊ฐ์ ์ผ๋ก ๋ณผ ์ ์๋ attribute๋ฅผ ์ ๊ณตํ์ฌ item์ ์ ๊ทผ์ฑ์ ์ ์ดํ๊ณ ๊ฒ์ํ ์ ์๋๋ก ํฉ๋๋ค.
์์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด ํค์ฒด์ธ ์๋น์ค๋ ๋์คํฌ์ ์ ์ฅ๋ ์ํธํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ ํค์ฒด์ธ์์ ๋ฐ์ดํฐ ์ํธํ ๋ฐ ์ ์ฅ(attributes ํฌํจ)์ ์ฒ๋ฆฌํฉ๋๋ค. ์ธ์ฆ๋ ํ๋ก์ธ์ค๋ ํค์ฒด์ธ ์๋น์ค๋ฅผ ์ฌ์ฉํ์ฌ ํญ๋ชฉ์ ์ฐพ๊ณ ํด๋น ๋ฐ์ดํฐ๋ฅผ ํด๋ ํฉ๋๋ค.
Keychain Item Class
Keychain item์ ์ํธ, ์ํธ ํค ๋ฐ ์ธ์ฆ์์ ๊ฐ์ด ์ ์ฅํ๋ ๋ฐ์ดํฐ์ ์ข ๋ฅ์ ๋ฐ๋ผ ๋ค์ํ ํด๋์ค๋ก ์ ๊ณต๋ฉ๋๋ค.
item์ ํด๋์ค๋ ์ ์ฉ๋๋ attribute๋ฅผ ๊ฒฐ์ ํ๊ณ ์์คํ ์ด ๋์คํฌ์์ ๋ฐ์ดํฐ๋ฅผ ์ํธํํด์ผ ํ๋์ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ ์ ์๋๋ก ํฉ๋๋ค. (์๋ฅผ ๋ค์ด, ์ํธ๋ ์ํธํ๊ฐ ํ์ํ์ง๋ง ์ธ์ฆ์๋ ๋น๋ฐ์ด ์๋๊ธฐ ๋๋ฌธ์ ์ํธํ๋์ง ์์ต๋๋ค.)
key(kSecClass)-value ์(Dictionary ํ์ )์ ์ด์ฉํด ์๋ก์ด item์ ํด๋์ค๋ฅผ ์ง์ ํ๊ณ , item์ ์กฐํ(์ ๋ฐ์ดํธ or ์ญ์ ์)ํ๋ ๊ฒฝ์ฐ๋ ์ด์ ๋์ผํ ์์ ์ฌ์ฉํฉ๋๋ค.
Keychain์ ์ ์ฅํ๋ ๋ฐ์ดํฐ ์ข ๋ฅ(value๊ฐ์ ํด๋น)
- kSecClassGenericPassword : ์ผ๋ฐ ์ํธ ํญ๋ชฉ์ ๋ํ๋ด๋ ๊ฐ
- kSecClassInternetPassword : ์ธํฐ๋ท ๋น๋ฐ๋ฒํธ ํญ๋ชฉ์ ๋ํ๋ด๋ ๊ฐ
- kSecClassCertificate : ์ธ์ฆ์ ํญ๋ชฉ์ ๋ํ๋ด๋ ๊ฐ
- kSecClassIdentity: ID ํญ๋ชฉ์ ๋ํ๋ด๋ ๊ฐ
- kSecClassKey : ์ํธํ ํค ํญ๋ชฉ์ ๋ํ๋ด๋ ๊ฐ
Keychain์ ์ด์ฉํ ์ฌ์ฉ์ ์ ๋ณด ๊ด๋ฆฌ
1. ์ฑ์ ์ฒ์ ์คํํ ๊ฒฝ์ฐ(์ธ์ฆ์ด ํ์ํ ๊ฒฝ์ฐ)
์ฑ์ ์ฒ์ ์คํํ๋ค๋ฉด Keychain์ ์ด๋ ํ ์ ๋ณด๋ ์ ์ฅ๋์ด์์ง ์์ ์ํ์ ๋๋ค.
SecItemCopyMatcing(_:_:) ๋ฉ์๋๋ฅผ ์ด์ฉํด ๊ฒ์์ ํ๋ฉด ๋ง๋ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์ ์ ์๊ธฐ ๋๋ฌธ์ ์ ๋ค์ด์ด๊ทธ๋จ์ ์ค๋ฅธ์ชฝ ํ๋ก์ฐ๋ฅผ ์งํํ๊ฒ ๋ฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ธ์ฆํ๋ credential์ ์ ๊ณตํ๋ฉด SecItemAdd(_:_:) ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ณด๋ฅผ ์ ์ฅํฉ๋๋ค.
์ด์ ์ฑ์ ์ผ๋ฐ ๋คํธ์ํฌ ์ก์ธ์ค๋ฅผ ๊ณ์ํฉ๋๋ค. ๋์ค์ ์๋ฒ๊ฐ ์ฌ์ธ์ฆ์ ์๊ตฌํ ๋ ์ฑ์ ์ฌ์ฉ์์๊ฒ ์ธ์ฆ์ ์๊ตฌํ๋ ๋์ ํค์ฒด์ธ์์ credential์ ๊ฒ์ํ ์ ์์ต๋๋ค.
2. ์ธ์ฆ์ ์ฑ๊ณตํ ๊ฒฝ์ฐ
์ฑ์ ์ฒ์ ์คํํ๋ ๊ฒฝ์ฐ๊ฐ ์๋๋ผ๋ฉด, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ์ค๊ฐ ํ๋ก์ฐ๋ฅผ ์งํํ๊ฒ ๋๋๋ฐ, ์ด ๊ฒฝ์ฐ๋ ์ฌ์ฉ์ ์ํธ ์์ฉ์ด ํ์ํ์ง ์์ต๋๋ค.
SecItemCopyMatching(_:_:) ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํค์ฒด์ธ์์ ์ํธ๋ฅผ ๊ฒ์ํฉ๋๋ค. ์ํธ๊ฐ ๋ฐ๊ฒฌ๋์ด ์ฑ์ด ์ธ์ฆ์ ์ฑ๊ณตํ๋ค๋ฉด ์ฌ์ฉ์๊ฐ ๊ด์ฌํ์ง ์๊ณ ๊ณ์ ์์ ์ ์งํํ ์ ์์ต๋๋ค.
3. ์ธ์ฆ์ ์คํจํ ๊ฒฝ์ฐ
๋๋๋ก ์ฌ์ฉ์๋ ์ฑ์ ๋ฒ์ ๋ฐ์์ credential์ ๋ณ๊ฒฝํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฑ์ ์ฌ์ฉํ๋ค๊ฐ ํด๋น ์๋น์ค์ ์น์ฌ์ดํธ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฐ๊ฟ ์ ์์ต๋๋ค. ๋น๋ฐ๋ฒํธ๊ฐ ๋ฐ๋ ์ดํ์๋ Keychain์์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฐพ๋๋ผ๋ ์ธ์ฆ์์ ์คํจํ๊ฒ ๋๊ธฐ ๋๋ฌธ์ ์ผ์ชฝ ํ๋ก์ฐ๋ฅผ ์งํํ๊ฒ ๋ฉ๋๋ค. ํด๋น ํ๋ก์ฐ์์๋ SecItemUpdate(_:_:) ๋ฉ์๋๋ฅผ ํตํด ํ์ฌ ์ ์ฅ๋ value๋ฅผ ์๋กญ๊ฒ ์ธ์ฆํ credential์ value๋ก ๋ณ๊ฒฝํ๊ฒ ๋ฉ๋๋ค.
4. Keychain ๋ฐ์ดํฐ ์ญ์
SecItemDelete(_:) ๋ฉ์๋๋ฅผ ์ด์ฉํด ํค์ฒด์ธ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ์ ์์ต๋๋ค.
์ฌ์ฉ ์์
ํค์ฒด์ธ ํด๋์ค ์ ์
import Foundation
import Security
final class Keychain {
// Create
static func create(key: String, data: String) {
let query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key,
kSecValueData: data.data(using: .utf8, allowLossyConversion: false) as Any
]
SecItemDelete(query) // Keychain์ Key๊ฐ์ ์ค๋ณต์ด ์๊ธฐ๋ฉด ์ ์ฅํ ์ ์๊ธฐ ๋๋ฌธ์ ๋จผ์ Delete
let status = SecItemAdd(query, nil)
assert(status == noErr, "Failed to save Token")
}
// Read
static func read(key: String) -> String? {
let query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key,
kSecReturnData: kCFBooleanTrue as Any,
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 {
#if DEBUG
print("Failed to loading, status code = \(status)")
#endif
return nil
}
}
// Delete
static 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)")
}
}
ํค์ฒด์ธ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ๊ฐ์ ธ์ค๊ธฐ
// AuthAPIService
func login() -> Single<Void> {
return oauthService
.authorize()
.do { oauthAuthentication in
#if DEBUG
print("โจ OAuth ์ธ์ฆ ์ฑ๊ณต) \(oauthAuthentication)")
#endif
let authType = oauthAuthentication.oauthType.rawValue
Keychain.create(key: Keychain.Keys.socialType, data: authType)
}
.map { $0.toSignInRequestDTO() }
.flatMap(signIn)
.do { dto in
if let dto {
UserDefaults.userId = dto.id
// ํค์ฒด์ธ์ ํ ํฐ ์ ์ฅ
Keychain.create(key: Keychain.Keys.accessToken, data: dto.accessToken)
Keychain.create(key: Keychain.Keys.refreshToken, data: dto.refreshToken)
} else {
#if DEBUG
print("ํฌ๊ทธํฌ๊ทธ ์๋ฒ ์ธ์ฆ ์คํจ")
#endif
// TODO: ๋คํธ์ํฌ ์ค๋ฅ ํ์
๋์์ฃผ๊ธฐ
}
}
.map { _ in () }
}
// DefaultAppCoordinator
func start() {
// ์ฑ์ ์์์ ์ค์ (keychain์ accessToken ์ ์ฅ ์ฌ๋ถ ํ์ธ)
if Keychain.read(key: Keychain.Keys.accessToken) != nil {
showMapFlow()
} else {
showLoginFlow()
}
}
Keychain ํด๋์ค์ ํค์ฒด์ธ ๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ๊ด๋ฆฌ๋ฅผ ์ํ ๋ฉ์๋๋ค์ ์ ์ํด๋๊ณ ํ์ํ ๊ณณ์์ ์ฌ์ฉํด์ฃผ๋ ๋ฐฉ์์ผ๋ก ํ ํฐ ์ ์ฅ ๋ก์ง์ ๊ตฌํํ๋๋ฐ์, ๋ ์ด์ ์์๋์ง ์์ ํด๋์ค์ด๊ธฐ ๋๋ฌธ์ final ํค์๋๋ฅผ ๋ถ์ฌ์ฃผ์๊ณ , ํค์ฒด์ธ ํด๋์ค์ ๋ฉ์๋๋ค๋ ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ ์ํ์ง ์๊ณ ํธ์ถํด์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ static func์ผ๋ก ์ ์ํ์ต๋๋ค.
ํค์ฒด์ธ ๋ฉ์๋ ํ์ฉ๋ฐฉ๋ฒ์ ๊ณต์๋ฌธ์์ ์์ธํ ๋์์์ผ๋ ๊ณต์๋ฌธ์๋ง ์ฐธ๊ณ ํด๋ ์ดํดํ๊ณ ํค์ฒด์ธ ์๋น์ค๋ฅผ ์ด์ฉํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค!
ex) Adding a Password to the Keychain
[์ฐธ๊ณ ์๋ฃ]
'๐ iOS > iOS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS] TDD์ Unit Test (3) | 2023.12.14 |
---|---|
[iOS] iOS ํ์ผ ์์คํ (1) | 2023.11.28 |
[iOS/Architecture] Coordinator Pattern (2) | 2022.12.11 |
[iOS] ReactorKit์ด๋? (4) | 2022.08.03 |
[iOS] UserDefaults๋? (3) | 2022.01.06 |