Token 유효성 체크 하고, 갱신 하고, 다시 요청.

Zion·2023년 5월 1일
0

Token 유효성 체크.

토큰이 X 기간동안 파기된다면, 유저가 알아차리지 못하게 갱신해서 샤용해야한다.

이러기위해선, Requset시에 status code가 401일 경우에 토큰 갱신을 판단해야한다.

갱신 후 원래 요청하려던 Request를 다시 요청해야한다.

sol) Alamofire의 RequestInterceptor를 이용해서 해결 할 수 있다.

RequestInterceptor 작동 로직

설명

  1. request -> (by URLRequestConvertible) -> URLRequest
  1. RequestInterceptor : URLReqeust가 만들어지면, Session과 연관된RequestInterceptor에 전달됨.
    RequestInterceptorURLReqeust를 수정할 수 있다.(header니 인증정보,etc.. 가 필요하다면)

  2. Session: 그런 다음. SessionURLRequest를 서버에 보낸다. 만약, error가 발생한다면(server error or network error), error는 RequestInterceptor에 리턴됨.

  3. RequestInterceptorretry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) 가 retry 여부를 RetryResult를 completion인자로 보냄에 따라 retry여부 결정된다.

  4. Session : RetryResult.retry 또는 retryWithDelay라면 Session은 자동으로 (토큰 or etc)수정된 URLRequestrequest를 재시도한다.
    RetryResult가 .doNotRetry라면, 재시도않고 실패한다.

How to use

class RequestInterceptor: RequestInterceptor {
    let retryLimit = 3
    let retryDelay: TimeInterval = 2
    var isRetrying = false
    
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var urlRequest = urlRequest
        if ConnectorHelper.isEndpointAPIA(urlRequest.url?.absoluteString) {
            urlRequest.setValue(LocalStorage.getTokenA(), forHTTPHeaderField: "authorization")
        } else {
            urlRequest.setValue(LocalStorage.getTokenB(), forHTTPHeaderField: ""authorization")
        }
        completion(.success(urlRequest))
    }
        
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
        let response = request.task?.response as? HTTPURLResponse
        if request.retryCount < self.retryLimit {
            if let statusCode = response?.statusCode, statusCode == 401, !isRetrying {
                self.isRetrying = true
                self.determineError(error: error, completion: completion)
            } else {
                completion(.retryWithDelay(self.retryDelay))
            }
        } else {
            NetworkClient.shared.session.cancelAllRequests()
            completion(.doNotRetry)
        }
    }
        
    private func determineError(error: Error, completion: @escaping (RetryResult) -> Void) {
        if let afError = error as? AFError {
            switch afError {
            case .responseValidationFailed(let reason):
                self.determineResponseValidationFailed(reason: reason, completion: completion)
            default:
                self.isRetrying = false
                completion(.retryWithDelay(self.retryDelay))
            }
        }
    }
        
    private func determineResponseValidationFailed(reason: AFError.ResponseValidationFailureReason, completion: @escaping (RetryResult) -> Void) {
        switch reason {
        case .unacceptableStatusCode(let code):
            switch code {
            case AuthenticationAction.refreshTokenA.rawValue:
                KeyApiConnector.refreshTokenA { _ in
                    self.isRetrying = false
                    completion(.retryWithDelay(self.retryDelay))
                }
            case AuthenticationAction.refreshTokenB.rawValue:
                KeyApiConnector.refreshTokenB { _ in
                    self.isRetrying = false
                    completion(.retryWithDelay(self.retryDelay))
                }
            default: // AuthenticationAction.logout.rawValue
                self.isRetrying = false
                NetworkClient.shared.session.cancelAllRequests()
                // Redirect to the login page
                completion(.doNotRetry)
            }
        default:
            self.isRetrying = false
            completion(.retryWithDelay(self.retryDelay))
        }
    }
}

retry의 completion을 인자로 전달해서 처리할수 있다..!

글 쓴 계기.

첫 시도에서 RequestInterceptor를 알지못해서 토큰 갱신을 request에서 해줬다.
그랬더니 토큰갱신이 필요할 때 여러곳에서 Network API가 request될 때 토큰 갱신이 여러번 이뤄졌다!.

토큰 갱신은 필요할때 한번만 이뤄져야 한다. 불필요한 토큰 갱신 요청은 없어야한다.
이를 해결하기위해 RequestInterceptor를 사용해 retry를 했다~!

참고

Alamofire : Advanced Usage

Gracefully handle retry requests using Alamofire

profile
어제보다만 나아지는

0개의 댓글