본인은 개발을 시작한지 얼마 되지 않은 대학생이지만, 최근에 토큰 재발급하는 로직 관련해서 어려움을 크게 겪어 누군가 도움이 되었으면 하는 마음에 글을 포스팅해본다 😁
Alamofire에서는 자체적으로 RequestIntercepter라는 프로토콜을 제공하고, 그 안에는 adapt, retry 메소드가 정의 되어있다. 따라서 우리는 이 두개의 메소드를 잘 활용한 후, api 호출할 때 인터셉트로 넣어주면 토큰을 쉽게 재발급할 수 있다.
작동 순서
request() -> adapt() -> 토큰 만료 -> retry() 에서 RefreshToken을 넣은 후 다시 reqeust() -> adapt() -> 서버에서 확인 후 토큰 갱신 -> retry() // 로컬에 토큰 업데이트 -> request() -> adapt() -> 성공순서이다.
나는 어떻게 구현했는지 한번 살펴보겠다.(본인이 개발을 시작한 지 얼마 안된 대학생이므로, 참고용으로만 보는것도 좋겠다!)
adapt
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
guard urlRequest.url?.absoluteString.hasPrefix("api 요청 사이트의 공통 주소") == true,
let accessToken = keychain.get("AccessToken") else {
completion(.success(urlRequest))
return
}
var urlRequest = urlRequest
urlRequest.setValue(accessToken, forHTTPHeaderField: "Authorization")
completion(.success(urlRequest))
}
본인이 요청하는 api 명세서에는 bearer을 붙힐 필요가 없어 빠져있으나, 본인의 프로젝트 명세서를 잘 확인한 후 앞에 bearer이 붙는지 안붙는지를 확인해 볼 필요가 있다.
completion은 어댑터가 호출이 완료되었음을 알리는 비동기 완료 핸들러이다. 본인은 accessToken이 필요한 호출 마다 intercepter를 넣어줬기 때문에, guard 문을 통과하지 못한다면, .success가 아닌 .failure를 넘겨준다. 정상적으로 진행이 될 경우 accessToken이 포함된 urlRequest 객체를 넘겨주어 그 다음 호출이 진행되도록 한다.
retry
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else {
completion(.doNotRetryWithError(error))
return
}
let url = "호출할 api 주소"
let headers: HTTPHeaders = [
"Authorization" : "저장 되어있는 RefreshToken"
]
AF.request(url, method: .post, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
let data = response.data
let json = JSON(data!)
switch response.result{
case .success(_):
self.keychain.clear()
self.keychain.set("리프레시_토큰", forKey: "RefreshToken")
self.keychain.set("액세스_토큰", forKey: "AccessToken")
completion(.retry)
case .failure(let error):
completion(.doNotRetryWithError(error))
}
}
처음 guard문에서 토큰 만료시 내려오는 에러 401이 아닌 다른 에러가 떨어질 경우 doNotRetryWithError(error)로 retry 하지 않고 넘어간다. retry 함수는 액세스 토큰이 만료 되었을 때 실행이 되는데, 나는 retry 함수가 실행될 때 리프레시 토큰으로 토큰 재발급 api를 호출한 후, 리프레시 토큰이 유효할 경우 새로 엑세스 토큰과 리프레시 토큰을 setting 해주고, 유효하지 않을 경우는 retry 하게 만들었다.
이 후에는 request에 intercepter 객체를 넣어주면 된다. 본인은 뒤에 request 괄호 닫힌 부분에 .validate()를 사용하지 않았어서 한참 헤멨으나, 이 글을 본 이들은 그런 실수를 하지 않았으면 좋겠다.
(validate()는 유효성 검사를 해줌 : https://velog.io/@chagmn/Swift-Alamofire-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B02)
본인의 글은 최대한 쉽게 써보려 노력했으며, 초보자의 관점에서 작성이 되었으므로 더 좋은 블로그 포스팅 주소도 남겨놓겠다 !
[참고자료]
https://ios-development.tistory.com/730?category=899471
https://velog.io/@jisng/Alamofire-Interceptor
Alamofire 공식 문서 : https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests-with-requestinterceptor
'iOS & Swift' 카테고리의 다른 글
[iOS] iOS 인터뷰 질문 - 1 (0) | 2022.05.11 |
---|---|
[Swift] 타입 캐스팅이란 ? (is, as) (0) | 2022.05.11 |
[Swift] Swift 공식문서 정리 - 5(옵셔널 체이닝) (0) | 2022.04.06 |
[Swift] Swift 공식문서 정리 - 4(열거형) (0) | 2022.04.06 |
[Swift] Swift 공식문서 정리 - 3(클래스 & 구조체) (0) | 2022.04.04 |