Skip to content

Commit

Permalink
feat: Codable 객체를 Keychain에 저장 가능하도록 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
rlarjsdn3 committed Oct 4, 2024
1 parent d91837d commit 09491b9
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ extension APISpecable {

guard
let queryParameters = try? queryParametersEncodable?.toDictionary()
?? self.queryParameters?.toDictionary() ?? [:]
?? self.queryParameters?.toDictionary() ?? [:]
else { throw URLGenerationError.invalidUrl }
queryParameters.forEach {
urlQueryItems.append(URLQueryItem(name: $0.key, value: "\($0.value)"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,16 @@ public extension BBNetworkHeader {
private extension BBNetworkHeader {

func fetchXAppKey() -> String {
// TODO: - Bundle에서 Key를 가져오도록 코드 수정하기
// TODO: - Bundle에서 가져오도록 코드 수정하기
return "7c5aaa36-570e-491f-b18a-26a1a0b72959"
}

func fetchXAuthTokenValue() -> String {
let keychain = KeychainWrapper.standard
guard
let string = keychain.string(forKey: .accessToken),
let encodedData = string.data(using: .utf8),
let accessToken = encodedData.decode(AccessToken.self)
else { return "" /* 예외 코드 작성 */ }

return accessToken.accessToken ?? "" /* 예외 코드 작성 */
if let authToken: AuthToken = keychain[.accessToken] {
return authToken.refreshToken
}
return "" // TODO: - 예외 코드 작성하기
}

func fetchXUserPlatform() -> String {
Expand All @@ -100,11 +97,10 @@ private extension BBNetworkHeader {

func fetchXuserId() -> String {
let userDefaults = UserDefaultsWrapper.standard
guard
let memberId = userDefaults.string(forKey: .memberId)
else { return "" /* 예외 코드 작성 */ }

return memberId /* 예외 코드 작성 */
if let memberId: String = userDefaults[.memberId] {
return memberId
}
return "" // TODO: - 예외 코드 작성하기
}

func fetchContentType() -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ final class BBNetworkInterceptor: Interceptor {

// MARK: - Properties

private let keychain = KeychainWrapper.standard
private let network = BBNoInterceptorNetworkSession()
private let keychain = KeychainWrapper.standard


// MARK: - Retry

public override func retry(
Expand All @@ -32,12 +33,12 @@ final class BBNetworkInterceptor: Interceptor {
return
}

guard let refreshToken = fetchRefreshToken(), let authToken = refreshAuthToken(refreshToken) else {
guard let authToken = fetchAuthToken(), let newAuthToken = refreshAuthToken(authToken) else {
completion(.doNotRetryWithError(error))
return
}

saveAuthToken(authToken)
saveAuthToken(newAuthToken)
completion(.retry)
}

Expand All @@ -59,26 +60,23 @@ final class BBNetworkInterceptor: Interceptor {
private extension BBNetworkInterceptor {

/// 키체인에 토큰 정보를 저장합니다.
func saveAuthToken(_ token: AccessToken) {
if let encodedString = token.encodeToString() {
keychain[.accessToken] = encodedString
}
@discardableResult
func saveAuthToken(_ token: AuthToken) -> Bool {
KeychainWrapper.standard.set(token, forKey: "accessToken")
}

/// 키체인으로부터 토큰 정보를 불러옵니다.
func fetchRefreshToken() -> String? {
guard let tokenString = keychain.string(forKey: .accessToken),
let encodedData = tokenString.encodeToData(),
let refreshToken = encodedData.decode(AccessToken.self)?.refreshToken else {
return nil
func fetchAuthToken() -> AuthToken? {
if let authToken: AuthToken = KeychainWrapper.standard.object(forKey: .accessToken) {
return authToken
}
return refreshToken
return nil
}

/// 토큰을 리프레시합니다.
func refreshAuthToken(_ refreshToken: String) -> AccessToken? {
func refreshAuthToken(_ refreshToken: AuthToken) -> AuthToken? {
let spec = BBAPISpec(
method: .get,
method: .post,
path: "/auth/refresh",
bodyParameters: ["refreshToken": "\(refreshToken)"],
headers: .unAuthorized
Expand All @@ -88,18 +86,20 @@ private extension BBNetworkInterceptor {
return nil
}

var tokenResult: AccessToken? = nil
let semaphore = DispatchSemaphore(value: 0)

network.session.request(urlRequest).validate().responseData { response in
if let data = response.data, let accessToken = data.decode(AccessToken.self) {
tokenResult = accessToken
var tokenResult: AuthToken? = nil
network.session.request(urlRequest).validate().response { response in
switch response.result {
case let .success(data):
if let accessToken = data?.decode(AuthToken.self) {
tokenResult = accessToken
}
case let .failure(error):
// MARK: - Logger로 로그 출력하기
debugPrint("🔴리프레시 실패: \(String(describing: error.errorDescription))")
}
// semaphore.signal()
}

// semaphore.wait()


return tokenResult
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
final public class KeychainWrapper {

// MARK: - Properties

public static let standard = KeychainWrapper()

private let SecMatchLimit: String! = kSecMatchLimit as String
Expand All @@ -30,7 +31,9 @@ final public class KeychainWrapper {
return Bundle.main.bundleIdentifier ?? "KeychainWrapper"
}()


// MARK: - Intializer

public init(
serviceName: String,
accessGroup: String? = nil
Expand Down Expand Up @@ -131,7 +134,7 @@ final public class KeychainWrapper {
forKey key: String,
withAccessibility accessibility: KeychainItemAccessibility? = nil,
isSynchronizable: Bool = false
) -> NSCoding? {
) -> (any NSCoding)? {
guard let keychainData = data(
forKey: key,
withAccessibility: accessibility,
Expand All @@ -143,6 +146,22 @@ final public class KeychainWrapper {
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSNumber.self, from: keychainData)
}

public func object<T>(
forKey key: String,
withAccessibility accessibility: KeychainItemAccessibility? = nil,
isSynchronizable: Bool = false
) -> T? where T: Decodable {
guard let keychainData = data(
forKey: key,
withAccessibility: accessibility,
isSynchronizable: isSynchronizable
) else {
return nil
}

return try? JSONDecoder().decode(T.self, from: keychainData)
}

public func data(
forKey key: String,
withAccessibility accessibility: KeychainItemAccessibility? = nil,
Expand Down Expand Up @@ -251,7 +270,7 @@ final public class KeychainWrapper {

@discardableResult
public func set(
_ value: NSCoding,
_ value: any NSCoding,
forKey key: String,
withAccessibility accessibiilty: KeychainItemAccessibility? = nil,
isSynchronizable: Bool = false
Expand All @@ -271,6 +290,25 @@ final public class KeychainWrapper {
}
}

@discardableResult
public func set(
_ value: any Encodable,
forKey key: String,
withAccessibility accessibiilty: KeychainItemAccessibility? = nil,
isSynchronizable: Bool = false
) -> Bool {
if let data = try? JSONEncoder().encode(value) {
return set(
data,
forKey: key,
withAccessibility: accessibiilty,
isSynchronizable: isSynchronizable
)
} else {
return false
}
}

@discardableResult
public func set(
_ value: Data,
Expand Down Expand Up @@ -384,7 +422,8 @@ final public class KeychainWrapper {
}


// MARK: - KeychainQueryDictionary
// MARK: - Keychain Query Dictionary

private func setupKeychainQueryDictionary(
forkey key: String,
withAccessibility accessibility: KeychainItemAccessibility? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,15 @@ public extension KeychainWrapper {
}
}

subscript(key: Key) -> NSCoding? {
subscript(key: Key) -> (any NSCoding)? {
get { object(forKey: key) }
set {
guard let value = newValue else { return }
set(value, forKey: key.rawValue)
}
}

subscript<T>(key: Key) -> T? where T: Codable {
get { object(forKey: key) }
set {
guard let value = newValue else { return }
Expand Down Expand Up @@ -89,7 +97,11 @@ public extension KeychainWrapper {
string(forKey: key.rawValue)
}

func object(forKey key: Key) -> NSCoding? {
func object(forKey key: Key) -> (any NSCoding)? {
object(forKey: key.rawValue)
}

func object<T>(forKey key: Key) -> T? where T: Decodable {
object(forKey: key.rawValue)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// AuthToken.swift
// Core
//
// Created by 김건우 on 10/3/24.
//

import Foundation

public struct AuthToken: Codable {

public let accessToken: String
public let refreshToken: String
public let isTemporaryToken: Bool

public init(accessToken: String, refreshToken: String, isTemporaryToken: Bool) {
self.accessToken = accessToken
self.refreshToken = refreshToken
self.isTemporaryToken = isTemporaryToken
}

}

extension AuthToken: Equatable { }
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
final public class UserDefaultsWrapper {

// MARK: - Properties

public static let standard = UserDefaultsWrapper()

private let userDefaults: UserDefaults!
Expand All @@ -20,7 +21,9 @@ final public class UserDefaultsWrapper {
"UserDefaultsWrapper"
}()


// MARK: - Intializer

convenience init() {
self.init(suitName: UserDefaultsWrapper.defaultSuitName)
}
Expand Down Expand Up @@ -166,4 +169,25 @@ final public class UserDefaultsWrapper {
userDefaults.removePersistentDomain(forName: suitName)
}


// MARK: - Register

/// 해당 키에 값이 비어있다면 넣을 기본 값을 등록합니다.
///
/// 앱 델리게이트의 `application(_ application:didFinishLaunchingWithOptions:)` 메서드에서 등록해야 합니다.
/// - Parameter values: [UserDefaultsWrapper.Key: Any]
public func register(_ values: [Key: Any]) {
var newValues = [String: Any]()
values.forEach { newValues.updateValue($0.value, forKey: $0.key.rawValue) }
register(newValues)
}

/// 해당 키에 값이 비어있다면 넣을 기본 값을 등록합니다.
///
/// 앱 델리게이트의 `application(_ application:didFinishLaunchingWithOptions:)` 메서드에서 등록해야 합니다.
/// - Parameter values: [String: Any]
public func register(_ values: [String: Any]) {
userDefaults.register(defaults: values)
}

}
5 changes: 3 additions & 2 deletions 14th-team5-iOS/Core/Sources/Extensions/Dictionary+Ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@

import Foundation

public extension Dictionary where Key: RawRepresentable, Value: RawRepresentable {
public extension Dictionary where Key == BBNetworkParameterKey, Value == BBNetworkParameterValue {

/// RawReprsentable 프로토콜을 준수하는 Key와 Value를 가진 딕셔너리를 [String: Any]로 변환합니다.
/// - Returns: [String: Any]
///
/// - Authors: 김소월
func toDictionary() -> [String: Any] {
var dict = [String: Any]()
self.forEach { key, value in
dict.updateValue(value.rawValue as Any, forKey: "\(key)")
dict.updateValue(value.rawValue as Any, forKey: "\(key.rawValue)")
}
return dict
}
Expand Down
Loading

0 comments on commit 09491b9

Please sign in to comment.