티스토리 뷰

안녕하세요.

이번에 프로젝트를 진행하면서 JWT를 통한 인증 방식을 사용했는데

앱 같은 경우 accessToken의 유효기간이 있어 refreshToken을 사용해 토큰을 갱신 받아야 합니다.

때문에 오늘 알아볼 것은 좀 더 간편하게 accessToken을 갱신 받을 수 있는 방법에 대해 알아보겠습니다.

RequestInterceptor

Type that provides both `RequestAdapter` and `RequestRetrier` functionality.
`RequestAdapter` 및 `RequestRetrier` 기능을 모두 제공하는 유형입니다.

RequestInterceptor는 Alamofire 라이브러리에서 제공하는 프로토콜 입니다.

프로토콜을 보면, adapt와, retry 두 메서드가 extension을 통해 기본 구현이 되어있습니다.

adapt 메서드 같은 경우 request가 시작되고 네트워크와 통신 직전에 호출되며, adapt 내부에선 Token 값을 헤더에 주입합니다.

retry 같은 경우 토큰의 유효기간이 끝나 인증 실패로 Error가 반환되었을 경우, retry 내부에서 토큰을 갱신 받고 재 호출을 요청할 수 있습니다.

위 순서도와 같이 Request가 이뤄지게 됩니다.

이젠 메서드에 대해 좀 더 알아보고, 코드로 구현해 보겠습니다.

adapt 

Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
지정된 `URLRequest`를 어떤 방식으로 검사 및 조정하고 결과로 완료 핸들러를 호출합니다.

adapt는 RequestAdapter라는 프로토콜에 정의되어 있으며, RequestInterceptor가 채택하고 있습니다.

adapt 내부에선 위에서 말했듯 access Token 값을 request url 헤더에 주입하는 역할을 합니다.

직접 코드로 구현해 보면,

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
    var urlRequest = urlRequest
    if let token = TokenStorage.shared.accessToken {
        if urlRequest.headers.dictionary["Authorization"] == nil {
            urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
    }
    
    completion(.success(urlRequest))
}

저는 위와 같이 코드를 작성했습니다.

TokenStorage는 단순 토큰 값을 담고 있는 저장소이고, 주입된 URLRequest의 헤더에 Authorization 값이 없을 때 헤더에 Token 값을 주입하는 식으로 구현했습니다.(다른 api에서 Authorization에 APIKey 등이 들어갈 수 있기 때문에)

retry

Determines whether the `Request` should be retried by calling the `completion` closure.
'완료' 클로저를 호출하여 '요청'을 재시도해야 하는지 여부를 결정합니다.

retry의 경우 RequestRetrier에 정의되어 있으며, 역시 RequestInterceptor가 채택하고 있습니다.

retry 내부에서는 재시도 전 토큰을 갱신해 주는 코드를 구현하면 되겠습니다.

코드 작성 전에 먼저, 몇 가지 더 알아보자면, retry 같은 경우 api 자체 문제로 인해 계속적인 Error가 발생할 경우 무한 루프에 빠질 수 있습니다.

때문에, retryLimit와 같은 Int 변수를 통해 재시도 횟수를 규정지을 수 있습니다.

또한, retryDelay와 같이 TimeInterval 변수를 통해 재시도 시 딜레이를 줄 수 있습니다.

private let retryLimit = 2

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 {
        return completion(.doNotRetryWithError(error))
    }
    guard request.retryCount < retryLimit else { return completion(.doNotRetryWithError(error)) }
    Task {
        guard let refreshToken = TokenStorage.shared.refreshToken else {
            return completion(.doNotRetryWithError(error))
        }
        do {
            let api = API.refreshToken(refreshToken)
            let token = try await apiManager.request(api, type: TokenReissueResponse.self)
            TokenStorage.shared.setTokens(accessToken: token.accessToken, refreshToken: token.refreshToken) // 토큰 갱신 코드
            return completion(.retry)
        } catch {
            return completion(.doNotRetryWithError(error))
        }
        
    }
}

위 코드와 같이 Status Code가 401 일 때만 동작하며, 재시도 횟수를 2번으로 재한 했습니다.

토큰 갱신에 성공하면, completion을 통해. retry을 반환합니다.

토큰 갱신에 실패하면, .doNotRetryWithError(error)를 반환하고 request를 종료합니다.

여기서 retry, doNotRetryWithError(error)는 RetryResult 타입의 열거형 값입니다.

4가지의 케이스가 있으며,

retry: 재시도 요청

retryWithDelay(TimeInterval): TimeInterval 값만큼 딜레이를 가지고 재시도 요청

doNotRetry: 재시도하지 않고 종료

doNotRetryWithError(Error): 재시도하지 않고 종료 및 error 반환

이상입니다.

여기까지 RequestInterceptor에 대해 알아봤습니다.

틀린 정보나 더 나은 방법이 있다면, 저 한테도 공유해 주세요.👍

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함