👨🏻💻 오늘의 작업
[ 1. Utils 파일 내 전역 함수 및 변수 추가 ]
문자열에서 날짜 계산하는 함수와 날짜에서 문자열 계산하는 함수를 추가하였다.
해당 함수는 액세스 토큰 만료가 되었는지 확인하기 위해서 사용할 예정이다.
API URL을 사용하는 곳이 많아짐에 따라 Utils 파일에 전역변수로 추가하였다.
[ 2. PublicAPI 파일 내 apiUrl 수정 ]
Bundle.main...으로 사용하던 apiUrl 주소를 Utils 파일에 있는 전역 변수인 API_URL로 변경
[ 3. AuthorizedAPI 추가 ]
추후에 사용할 Auth API를 위한 AuthorizedAPI 추가
[ 4. NetworkManager AuthProvider 추가 ]
[ 5. AuthInterceptor 추가 ]
//
// AuthInterceptor.swift
// SoBunSoBun
//
// Created by 허성필 on 9/24/25.
//
import Foundation
import Alamofire
import Moya
final class AuthInterceptor: RequestInterceptor {
static let shared = AuthInterceptor()
private init() {}
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
// 저장된 액세스 토큰과 액세스 토큰 만료 시간을 가져오기
guard let accessToken = KeyChain.shared.get(key: "ACCESS_TOKEN"),
let accessTokenExpireAtKST = KeyChain.shared.get(key: "ACCESS_TOKEN_EXPIRE_AT_KST")
else {
// TODO: LogOut 구현하기 (ex 로그인 화면으로 이동)
return
}
let now = Date()
let isAccessExpired = stringToDate(string: accessTokenExpireAtKST,
format: "yyyy-MM-dd HH:mm:ss") < now
// 현재 시간과 accessToken의 만료 시간을 비교
if isAccessExpired {
completion(.failure(NSError(domain: "APIService", code: 401, userInfo: [NSLocalizedDescriptionKey: "액세스 토큰이 없음"])))
return
}
// 헤더에 accessToken을 담아서 전달
var urlRequest = urlRequest
urlRequest.headers.add(.authorization(bearerToken: accessToken))
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
print("retry 진입")
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
print("401 오류가 아님")
completion(.doNotRetryWithError(error))
return
}
guard let refreshTokenExpireAtKST = KeyChain.shared.get(key: "REFRESH_TOKEN_EXPIRE_AT_KST") else {
// TODO: LogOut 구현하기 (ex 로그인 화면으로 이동)
completion(.doNotRetry)
print("리프레시 토큰 만료 시간 정보 없음")
return
}
let now = Date()
let isRefreshExpired = stringToDate(string: refreshTokenExpireAtKST,
format: "yyyy-MM-dd HH:mm:ss") < now
// 현재 시간과 refreshToken의 만료 시간을 비교
if isRefreshExpired {
// TODO: LogOut 구현하기 (ex 로그인 화면으로 이동)
completion(.doNotRetry)
print("리프레시 토큰 만료")
return
}
if isRefreshing {
print("재발급 중")
completion(.retry)
} else {
isRefreshing = true
refreshToken() { [weak self] isSuccess in
guard let self = self else { return }
isRefreshing = false
if isSuccess {
print("액세스 토큰 재발급 완료")
completion(.retry)
} else {
print("리프레시 토큰 만료")
// TODO: 로그아웃 구현 (ex 로그인 화면으로 넘기기)
completion(.doNotRetry)
}
}
}
}
// 리프레시 토큰을 사용하여 액세스 토큰을 재발급 후 Keychain에 저장
private func refreshToken(completion: @escaping(Bool) -> Void) {
guard let refresh = KeyChain.shared.get(key: "REFRESH_TOKEN") else {
print("리프레시 토큰 없음")
completion(false)
return
}
let parameters: [String: Any] = ["refresh": refresh]
AF.request("\(API_URL)/auth/token/refresh",
method: .post,
parameters: parameters,
encoding: JSONEncoding.default)
.validate(statusCode: 200..<300)
.responseDecodable(of: UserModel.self) { response in
switch response.result {
case .success(let userModel):
KeyChain.shared.set(key: "ACCESS_TOKEN", value: userModel.accessToken)
KeyChain.shared.set(key: "ACCESS_TOKEN_EXPIRE_AT_KST", value: String(userModel.accessTokenExpiresAtKst))
completion(true)
case .failure(let error):
print(error.localizedDescription)
completion(false)
}
}
}
}
토큰 만료 시간과 현재 시간을 비교해서 만료되었다면 재발급을 요청하고
재발급 만료 시간과 현재 시간을 비교해서 만료되었다면 로그아웃을 진행하는 AuthInterceptor 추가
'iOS 팀 프로젝트 > 소분소분' 카테고리의 다른 글
Components 만들기 #2 (0) | 2025.10.03 |
---|---|
Components 만들기 #1 (0) | 2025.09.26 |
Localizable.xcstrings 줄 넘김 방법 (0) | 2025.09.19 |
디자인 시스템 추가 적용 (0) | 2025.09.18 |
디자인 시스템 추가 (0) | 2025.09.17 |