Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] make keychain #183

Merged
merged 4 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions KkuMulKum.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
78BD61202C43F557005752FD /* SwiftKeychainWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 78BD611F2C43F557005752FD /* SwiftKeychainWrapper */; };
78BD61272C446A97005752FD /* LoginTargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61262C446A97005752FD /* LoginTargetType.swift */; };
78BD612B2C4550A6005752FD /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612A2C4550A6005752FD /* Bundle.swift */; };
78BD612F2C4561B9005752FD /* KeychainAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD612E2C4561B9005752FD /* KeychainAccessible.swift */; };
78BD61312C456799005752FD /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD61302C456799005752FD /* KeychainService.swift */; };
A3DD9C3D2C41BAD000E58A13 /* MeetingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */; };
A3DD9C3E2C41BAD000E58A13 /* MeetingDummyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C342C41BAD000E58A13 /* MeetingDummyModel.swift */; };
A3DD9C3F2C41BAD000E58A13 /* MeetingListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DD9C362C41BAD000E58A13 /* MeetingListView.swift */; };
Expand Down Expand Up @@ -210,6 +212,8 @@
78B928792C29402E006D9942 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
78BD61262C446A97005752FD /* LoginTargetType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTargetType.swift; sourceTree = "<group>"; };
78BD612A2C4550A6005752FD /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
78BD612E2C4561B9005752FD /* KeychainAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessible.swift; sourceTree = "<group>"; };
78BD61302C456799005752FD /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
A3DD9C322C41BAD000E58A13 /* MeetingTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingTableViewCell.swift; sourceTree = "<group>"; };
A3DD9C342C41BAD000E58A13 /* MeetingDummyModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingDummyModel.swift; sourceTree = "<group>"; };
A3DD9C362C41BAD000E58A13 /* MeetingListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingListView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -548,6 +552,15 @@
path = Bundle;
sourceTree = "<group>";
};
78BD612D2C456162005752FD /* KeyChain */ = {
isa = PBXGroup;
children = (
78BD612E2C4561B9005752FD /* KeychainAccessible.swift */,
78BD61302C456799005752FD /* KeychainService.swift */,
);
path = KeyChain;
sourceTree = "<group>";
};
A3DD9C332C41BAD000E58A13 /* Cell */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -956,7 +969,6 @@
DD865B662C39210E00C351A2 /* Login */ = {
isa = PBXGroup;
children = (
78BD61212C440AC8005752FD /* Service */,
789873372C3D1B4800435E96 /* ViewController */,
789873362C3D1B3900435E96 /* VIewModel */,
789873352C3D1B3000435E96 /* View */,
Expand Down Expand Up @@ -1068,6 +1080,7 @@
DE254AA32C31107C00A4015E /* Resource */ = {
isa = PBXGroup;
children = (
78BD612D2C456162005752FD /* KeyChain */,
78BD612C2C455680005752FD /* Bundle */,
DEBA03302C3C2972002ED8F2 /* ViewController.swift */,
DD39768B2C41C36B00E2A4C4 /* Color.xcassets */,
Expand Down Expand Up @@ -1464,7 +1477,6 @@
DE6D4D112C3F14D80005584B /* MeetingInfoBannerView.swift in Sources */,
DE6D4D122C3F14D80005584B /* MeetingInfoView.swift in Sources */,
DD30721E2C3C0CC800416D9F /* PromiseInfoResponseModel.swift in Sources */,
78BD61232C440AD5005752FD /* AuthService.swift in Sources */,
DD931B722C3DA92700526452 /* EnterReadyInfoButtonView.swift in Sources */,
DD41BEFF2C41DAA40095A068 /* TardyEmptyView.swift in Sources */,
A3FB18512C3BF531001483E5 /* RegisterMeetingsResponseModel.swift in Sources */,
Expand Down Expand Up @@ -1532,6 +1544,7 @@
DEBA032F2C3C24F2002ED8F2 /* ModelType.swift in Sources */,
789AD4B52C3C0147002E2688 /* ResissueResponseModel.swift in Sources */,
DE6D4D0F2C3F14D80005584B /* MeetingInfoService.swift in Sources */,
78BD612F2C4561B9005752FD /* KeychainAccessible.swift in Sources */,
DD39768A2C41C2AD00E2A4C4 /* HomeViewController.swift in Sources */,
DED5DBF42C34539A006ECE7E /* BaseTableViewCell.swift in Sources */,
78B9286E2C29402C006D9942 /* SceneDelegate.swift in Sources */,
Expand All @@ -1545,9 +1558,9 @@
DD41BEFC2C41D54D0095A068 /* TardyPenaltyView.swift in Sources */,
789873322C3D1A7B00435E96 /* LoginViewController.swift in Sources */,
782B40722C3DBFA3008B0CA7 /* ProfileSetupViewModel.swift in Sources */,
DDAF1C8F2C3D6E3D008A37D3 /* BasePromiseSegmentedControl.swift in Sources */,
DDAF1C8F2C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift in Sources */,
DD49099C2C441719003ED304 /* TardyService.swift in Sources */,
782B40722C3DBFA3008B0CA7 /* ProfileViewModel.swift in Sources */,
782B40722C3DBFA3008B0CA7 /* ProfileSetupViewModel.swift in Sources */,
DDAF1C8F2C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift in Sources */,
DE32D1D22C3BF703006848DF /* LoginUserResponseModel.swift in Sources */,
DE9E18892C3BC91000DB76B4 /* ResponseBodyDTO.swift in Sources */,
Expand All @@ -1560,6 +1573,7 @@
DD931B6E2C3DA27F00526452 /* ParticipantCollectionViewCell.swift in Sources */,
DD3976882C41C2AD00E2A4C4 /* UpcomingEmptyView.swift in Sources */,
DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */,
78BD61312C456799005752FD /* KeychainService.swift in Sources */,
DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */,
DD3976872C41C2AD00E2A4C4 /* TodayPromiseView.swift in Sources */,
A3DD9C5A2C43F99800E58A13 /* SetReadyInfoView.swift in Sources */,
Expand Down
4 changes: 4 additions & 0 deletions KkuMulKum/KkuMulKum.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
<array>
<string>Default</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)KkuMulKum.yizihn</string>
</array>
</dict>
</plist>
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ struct JwtTokenDTO: Codable {
let accessToken: String
let refreshToken: String
}

struct RefreshTokenResponseModel: ResponseModelType {
let accessToken: String
let refreshToken: String
}
22 changes: 17 additions & 5 deletions KkuMulKum/Network/DTO/TargetType/LoginTargetType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import Moya
enum LoginTargetType {
case appleLogin(identityToken: String, fcmToken: String)
case kakaoLogin(accessToken: String, fcmToken: String)
case refreshToken(refreshToken: String)
}

extension LoginTargetType: TargetType {

var method: Moya.Method {
.post
}

var baseURL: URL {
guard let privacyInfo = Bundle.main.privacyInfo,
let urlString = privacyInfo["BASE_URL"] as? String,
Expand All @@ -25,11 +31,12 @@ extension LoginTargetType: TargetType {
}

var path: String {
return "/api/v1/auth/signin"
}

var method: Moya.Method {
return .post
switch self {
case .appleLogin, .kakaoLogin:
return "/api/v1/auth/signin"
case .refreshToken:
return "/api/v1/auth/reissue"
}
}

var task: Task {
Expand All @@ -44,6 +51,8 @@ extension LoginTargetType: TargetType {
parameters: ["provider": "KAKAO", "fcmToken": fcmToken],
encoding: JSONEncoding.default
)
case .refreshToken:
return .requestPlain
}
}

Expand All @@ -53,6 +62,9 @@ extension LoginTargetType: TargetType {
return ["Authorization": identityToken, "Content-Type": "application/json"]
case .kakaoLogin(let accessToken, _):
return ["Authorization": accessToken, "Content-Type": "application/json"]
case .refreshToken(let refreshToken):
return ["Authorization": refreshToken, "Content-Type": "application/json"]
}
}
}

46 changes: 46 additions & 0 deletions KkuMulKum/Resource/KeyChain/KeychainAccessible.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// KeychainWrapper.swift
// KkuMulKum
//
// Created by μ΄μ§€ν›ˆ on 7/15/24.
//

import Foundation
import SwiftKeychainWrapper

protocol KeychainAccessible {
func saveToken(_ key: String, _ value: String)
func saveExpiresIn(_ key: String, _ value: Int)
func getToken(_ key: String) -> String?
func getExpiresIn(_ Key: String) -> Int?
func remove(_ key: String)
func removeAll()
}

class DefaultKeychainAccessible: KeychainAccessible {
private let keychain = KeychainWrapper.standard

func saveToken(_ key: String, _ value: String) {
keychain.set(value, forKey: key)
}

func saveExpiresIn(_ key: String, _ value: Int) {
keychain.set(value, forKey: key)
}

func getToken(_ key: String) -> String? {
keychain.string(forKey: key)
}

func getExpiresIn(_ Key: String) -> Int? {
keychain.integer(forKey: Key)
}

func remove(_ key: String) {
keychain.removeObject(forKey: key)
}

func removeAll() {
keychain.removeAllKeys()
}
}
136 changes: 136 additions & 0 deletions KkuMulKum/Resource/KeyChain/KeychainService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//
// KeychainService.swift
// KkuMulKum
//
// Created by μ΄μ§€ν›ˆ on 7/15/24.
//

import Foundation
import SwiftKeychainWrapper
import Security

protocol KeychainService {
var accessToken: String? { get set }
var refreshToken: String? { get set }
func removeAllTokens()
func verifyKeychainAccess()
}

class DefaultKeychainService: KeychainService {
static let shared = DefaultKeychainService()

private let keychain: KeychainWrapper

private struct Key {
static let accessToken = "accessToken"
static let refreshToken = "refreshToken"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

structλŠ” 생성이 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ—, 일반적으둜 enum으둜 μ„ μ–Έν•˜κ³€ ν•œλ‹΅λ‹ˆλ‹€.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ν—‰ μ°Έκ³ ν•˜κ²ŸμŠ΅λ‹ˆλ‹€!


init() {
let serviceName = Bundle.main.privacyInfo?["ServiceName"] as? String ?? Bundle.main.bundleIdentifier ?? "DefaultServiceName"
self.keychain = KeychainWrapper(serviceName: serviceName)
print("Keychain initialized with service name: \(serviceName)")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 뢀뢄은 μ‚­μ œν•΄λ„ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

λ°˜μ˜μ™„


var accessToken: String? {
get {
let token = keychain.string(forKey: Key.accessToken)
print("Reading Access Token: \(token ?? "nil")")
return token
}
set {
if let newValue = newValue {
let success = keychain.set(newValue, forKey: Key.accessToken)
print("Setting Access Token: \(newValue)")
if success {
print("Access Token saved successfully")
// μ €μž₯ ν›„ μ¦‰μ‹œ 읽어 확인
if let savedToken = keychain.string(forKey: Key.accessToken) {
print("Verified Access Token: \(savedToken)")
} else {
print("Failed to verify Access Token after saving")
}
} else {
print("Failed to save Access Token")
printKeychainError(forKey: Key.accessToken)
}
} else {
let success = keychain.removeObject(forKey: Key.accessToken)
print(success ? "Access Token removed successfully" : "Failed to remove Access Token")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뎁슀 μ’€ μ€„μ—¬μ£Όμ„Έμš”!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

λ°˜μ˜μ™„

}
}

var refreshToken: String? {
get {
let token = keychain.string(forKey: Key.refreshToken)
print("Reading Refresh Token: \(token ?? "nil")")
return token
}
set {
if let newValue = newValue {
let success = keychain.set(newValue, forKey: Key.refreshToken)
print("Setting Refresh Token: \(newValue)")
if success {
print("Refresh Token saved successfully")
// μ €μž₯ ν›„ μ¦‰μ‹œ 읽어 확인
if let savedToken = keychain.string(forKey: Key.refreshToken) {
print("Verified Refresh Token: \(savedToken)")
} else {
print("Failed to verify Refresh Token after saving")
}
} else {
print("Failed to save Refresh Token")
printKeychainError(forKey: Key.refreshToken)
}
} else {
let success = keychain.removeObject(forKey: Key.refreshToken)
print(success ? "Refresh Token removed successfully" : "Failed to remove Refresh Token")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 뎁슀 μ€„μ—¬μ£Όμ„Έμš”!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

λ°˜μ˜μ™„

}
}

func removeAllTokens() {
keychain.removeObject(forKey: Key.accessToken)
keychain.removeObject(forKey: Key.refreshToken)
print("All tokens removed from keychain")
}

func verifyKeychainAccess() {
let testKey = "TestKeychainAccess"
let testValue = "TestValue"

// 킀체인에 ν…ŒμŠ€νŠΈ κ°’ μ €μž₯
let saveSuccess = keychain.set(testValue, forKey: testKey)
if saveSuccess {
print("Test value saved to keychain successfully")

// μ €μž₯된 κ°’ 읽기
if let retrievedValue = keychain.string(forKey: testKey) {
print("Test value retrieved successfully: \(retrievedValue)")
} else {
print("Failed to retrieve test value")
}

// ν…ŒμŠ€νŠΈ κ°’ μ‚­μ œ
let removeSuccess = keychain.removeObject(forKey: testKey)
print(removeSuccess ? "Test value removed successfully" : "Failed to remove test value")
} else {
print("Failed to save test value to keychain")
printKeychainError(forKey: testKey)
}
}

private func printKeychainError(forKey key: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecAttrService as String: keychain.serviceName,
kSecReturnData as String: true
]

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
print("Keychain error for key '\(key)': \(SecCopyErrorMessageString(status, nil) ?? "Unknown error" as CFString)")
}
}
Loading