Skip to content

Commit

Permalink
[#72] 토큰 reissue 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
kimsoomin20221789 committed Jul 26, 2024
1 parent e20cac2 commit 85a2f00
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 44 deletions.
94 changes: 56 additions & 38 deletions Spon-Us/Auth/AuthPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,74 @@ import Moya

final class AuthPlugin: PluginType {
var onRetrySuccess: (() -> Void)?
var onRetryFail: (() -> Void)?

private var originalRequest: URLRequest?

func willSend(_ request: RequestType, target: TargetType) {
originalRequest = request.request
}

func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
switch result {
case .success(let response):
if response.statusCode == 401 {
// 토큰이 만료된 경우 -> Reissue
TokenManager.shared.refreshAccessToken { success in
if success {
// 원래 요청 재시도.
guard var newRequest = response.request else {
print("🤢 원래 요청을 생성할 수 없습니다.")
return
}
if let newToken = TokenManager.shared.accessToken {
newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization")
handle401Error(for: response)
}
case .failure(let error):
if error.response?.statusCode == 401 {
handle401Error(for: error.response!)
} else {
print("Request failed with error: \(error)")
}
}
}

private func handle401Error(for response: Response) {
// 토큰 재발급 시도
TokenManager.shared.refreshAccessToken { success in
if success {
// 원래 요청 재시도
guard var newRequest = self.originalRequest else {
print("🤢 원래 요청을 생성할 수 없습니다.")
self.onRetryFail?()
return
}
if let newToken = TokenManager.shared.accessToken {
newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization")
}
let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in
if let error = error {
print("🤢 재시도 요청 실패: \(error)")
DispatchQueue.main.async {
self.onRetryFail?()
}
let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in
if let error = error {
print("🤢 재시도 요청 실패: \(error)")
} else if let response = response as? HTTPURLResponse {
if (200...299).contains(response.statusCode) {
print("😊 재시도 요청 성공: \(response.statusCode)")
DispatchQueue.main.async {
self.onRetrySuccess?()
}
} else {
print("🤢 재시도 요청 실패: \(response.statusCode)")
}
} else {
print("🤢 알 수 없는 오류 발생")
} else if let response = response as? HTTPURLResponse {
if (200...299).contains(response.statusCode) {
print("😊 재시도 요청 성공: \(response.statusCode)")
DispatchQueue.main.async {
self.onRetrySuccess?()
}
} else {
print("🤢 재시도 요청 실패: \(response.statusCode)")
DispatchQueue.main.async {
self.onRetryFail?()
}
}
task.resume()
} else {
print("🤢 refreshToken 만료!!")
print("🤢 알 수 없는 오류 발생")
DispatchQueue.main.async {
self.onRetryFail?()
}
}
}
task.resume()
} else {
print("🤢 refreshToken 만료!!")
DispatchQueue.main.async {
self.onRetryFail?()
}
}
case .failure(let error):
print("Request failed with error: \(error)")
}
}
}


// MARK: Plugin 사용법!
//let authPlugin = AuthPlugin()
// onRetrySuccess -> 작동 시킨 후 UI적으로 재시동시킬것
//authPlugin.onRetrySuccess = { [weak self] in
// self?.fetchReservationAPI(for: self?.selectedDate ?? Date())
// self?.fetchWeekReservationAPI(for: self?.selectedDate ?? Date())
//}
//self.provider = MoyaProvider<NewReservationAPI>(plugins: [authPlugin])
36 changes: 34 additions & 2 deletions Spon-Us/Auth/TokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
//

import Foundation
import Moya

class TokenManager {
static let shared = TokenManager()

private let provider = MoyaProvider<SponusAPI>(plugins: [NetworkLoggerPlugin()])

private init() { }

var accessToken: String? {
Expand Down Expand Up @@ -55,9 +57,39 @@ class TokenManager {
UserDefaults.standard.removeObject(forKey: "fcmToken")
}

// reissue API 붙이기
func refreshAccessToken(completion: @escaping (Bool) -> Void) {
guard let refreshToken = refreshToken else {
completion(false)
return
}

provider.request(.getReissue(refreshToken: refreshToken)) { result in
switch result {
case .success(let response):
if response.statusCode == 400 {
print("🚨토큰 재발급 네트워크 실패: 상태 코드 400 -> refresh 토큰 만료")
completion(false)
return
}
do {
if let loginResponse = try? response.map(LoginResponse.self) {
self.accessToken = loginResponse.content.accessToken
self.refreshToken = loginResponse.content.refreshToken
completion(true)
print("🚨reissue API 성공")
print(" access:\(loginResponse.content.accessToken)")
print(" refresh:\(loginResponse.content.refreshToken)")
}
} catch {
print("🚨reissue API 네트워크 매핑 실패")
completion(false)
}
case .failure(let error):
print("🚨reissue API 네트워크 실패")
print("refreshToken:\(refreshToken)")
completion(false)
}
}
}

// refreshToken 만료됐는지 확인하는 로직 붙이기
Expand Down
23 changes: 21 additions & 2 deletions Spon-Us/Home/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,27 @@ import Moya

@Observable
final class HomeViewModel {
let provider: MoyaProvider<SponusAPI> = .init()
private let authPlugin = AuthPlugin()
private var provider: MoyaProvider<SponusAPI>!

init() {
setupProvider()
setupAuthPluginCallbacks()
}

private func setupProvider() {
self.provider = MoyaProvider<SponusAPI>(plugins: [authPlugin])
}

private func setupAuthPluginCallbacks() {
authPlugin.onRetrySuccess = { [weak self] in
// 재시도 성공 시 필요한 동작 추가.
}
authPlugin.onRetryFail = { [weak self] in
// loginVM.logout()
}
}

var companies: [OrganizationModel] = []
var clubs: [OrganizationModel] = []

Expand Down Expand Up @@ -164,7 +183,7 @@ final class HomeViewModel {
case .all:
filteredClubs = clubs
case .advertisingAndMarketing:
filteredClubs = clubs.filter { $0.subTypes.contains("AD_MARKETING") }
filteredClubs = clubs.filter { $0.subTypes.contains("AD_MARKETING") }
case .design:
filteredClubs = clubs.filter { $0.subTypes.contains("DESIGN") }
case .iTAndSoftware:
Expand Down
13 changes: 12 additions & 1 deletion Spon-Us/Network/SponusAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum SponusAPI {
case deleteKeyword(keyword: String)
case deleteSearch
case getVerifyEmail(email: String)
case getReissue(refreshToken: String)
}

extension SponusAPI: TargetType {
Expand Down Expand Up @@ -60,6 +61,8 @@ extension SponusAPI: TargetType {
return "/api/v2/auth/login"
case .getVerifyEmail:
return "/api/v2/auth/verify-email"
case .getReissue:
return "/api/v2/auth/reissue"
}
}

Expand Down Expand Up @@ -93,6 +96,8 @@ extension SponusAPI: TargetType {
return .post
case .getVerifyEmail:
return .get
case .getReissue:
return .get
}
}

Expand Down Expand Up @@ -126,6 +131,8 @@ extension SponusAPI: TargetType {
return Data()
case .getVerifyEmail:
return Data()
case .getReissue:
return Data()
}
}

Expand Down Expand Up @@ -163,7 +170,9 @@ extension SponusAPI: TargetType {
return .requestJSONEncodable(signUpDetails)
case .postLogin(loginDetails: let loginDetails):
return .requestJSONEncodable(loginDetails)
case .getVerifyEmail(email: let email):
case .getVerifyEmail:
return .requestPlain
case .getReissue:
return .requestPlain
}
}
Expand Down Expand Up @@ -205,6 +214,8 @@ extension SponusAPI: TargetType {
return ["Content-Type": "application/json"]
case .getVerifyEmail(email: let email):
return ["email": email]
case .getReissue(refreshToken: let refreshToken):
return ["RefreshToken": "\(refreshToken)"]
}
}
}
4 changes: 3 additions & 1 deletion Spon-Us/Spon_UsApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ struct Spon_UsApp: App {
var body: some Scene {
WindowGroup() {
if (vm.loginSuccess || (TokenManager.shared.isAutoLogin ?? false)) && !TokenManager.shared.isRefreshTokenExpired() {
ContentView()
ContentView().onAppear(perform: {
print("\(vm.loginSuccess), \(TokenManager.shared.isAutoLogin ?? false), \(!TokenManager.shared.isRefreshTokenExpired())")
})
} else {
OnBoardingView()
}
Expand Down

0 comments on commit 85a2f00

Please sign in to comment.