카테고리 없음

[iOS/Swift] Moya Interceptor 로 자동로그인 구현하기

게게겍 2024. 7. 25. 02:01

 

RequestInterceptor는 Alamofire에서 제공하는 프로토콜입니다.

Moya는 Alamofire를 wrapping한 라이브러리이기 때문에 해당 프로토콜을 사용할 수 있습니다.

RequestInterceptor에서 RequestAdapter과 RequestRetrier를 채택하고 있습니다. 이름 그대로 통신을 Interceptor하여 작업을 수행합니다.

또한 해당 프로토콜은 각각 adapt, retry 함수를 요구합니다.

그리고 RequestInterceptor는 extension을 통해 이 adapt와 retry의 기본 구현을 제공합니다.

adapt는 Request가 전송되기 전에 원하는 추가적인 작업을 수행할 수 있도록 하는 함수입니다. 즉, 이 함수에 다음과 같이 헤더에 넣을 토큰을 지정해주면 별도로 TargetType마다 헤더에 토큰을 넣어주지 않아도 자동으로 토큰이 들어가게 됩니다.

 

AuthInterceptor의 adapt와 retry 메서드

adapt 메서드

func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {

    var urlRequest = urlRequest
    if let accessToken = authService.getAccessToken() {
        urlRequest.addValue("Bearer \\(accessToken)", forHTTPHeaderField: "Authorization")
    }
    
    completion(.success(urlRequest))
}

 

이 adapt 메서드는 모든 네트워크 요청이 서버로 전송되기 전에 호출됩니다. 전체적인 로직으로는

  1. authService에서 저장된 액세스 토큰을 가져옵니다.
  2. 액세스 토큰이 있다면, 요청의 헤더에 "Authorization" 필드를 추가하고 "Bearer" 스키마와 함께 토큰을 설정합니다.
  3. 수정된 요청을 completion 핸들러를 통해 반환합니다.

이렇게 하면 모든 API 요청에 자동으로 인증 정보가 포함되어 전송됩니다.

 

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(.doNotRetry)
        return
    }

    guard let refreshToken = authService.getRefreshToken() else {
        authService.clearTokens()
        completion(.doNotRetry)
        return
    }

    provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in
        switch result {
        case .success(let response):
            do {
                let reissueResponse = try response.map(ResponseBodyDTO<ReissueModel>.self)
                if reissueResponse.success, let data = reissueResponse.data {
                    self?.authService.saveAccessToken(data.accessToken)
                    self?.authService.saveRefreshToken(data.refreshToken)
                    completion(.retry)
                } else {
                    self?.authService.clearTokens()
                    completion(.doNotRetry)
                }
            } catch {
                self?.authService.clearTokens()
                completion(.doNotRetry)
            }
        case .failure:
            self?.authService.clearTokens()
            completion(.doNotRetry)
        }
    }
}

retry 메서드는 네트워크 요청이 실패했을 때 호출됩니다.

  1. 응답 상태 코드가 401(Unauthorized)인지 확인합니다. 401이 아니면 재시도하지 않습니다.
  2. 리프레시 토큰을 가져옵니다. 없으면 저장된 토큰을 모두 삭제하고 재시도하지 않습니다.
  3. 리프레시 토큰을 사용해 새로운 액세스 토큰을 요청합니다.
  4. 새 토큰 발급에 성공하면 토큰을 저장하고 원래 요청을 재시도합니다.
  5. 실패하면 저장된 토큰을 모두 삭제하고 재시도하지 않습니다.

AuthInterceptor를 사용하기 위해서는 Moya Provider를 생성할 때 이를 설정해야 합니다.

swift
Copy
let provider = MoyaProvider<MyAPI>(plugins: [NetworkLoggerPlugin()], interceptor: AuthInterceptor.shared)

이렇게 설정하면 이 provider를 통해 이루어지는 모든 네트워크 요청에 AuthInterceptor가 적용됩니다.

자동로그인을 구현해보자!

  1. 자동 로그인과 액세스 토큰 사용

자동 로그인 구현:

func autoLogin(completion: @escaping (Bool) -> Void) {
    guard let refreshToken = authService.getRefreshToken() else {
        loginState.value = .notLogin
        completion(false)
        return
    }

    provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in
        switch result {
        case .success(let response):
            do {
                let reissueResponse = try response.map(ResponseBodyDTO<RefreshTokenResponseModel>.self)
                if reissueResponse.success, let data = reissueResponse.data {
                    self?.saveTokens(accessToken: data.accessToken, refreshToken: data.refreshToken)
                    self?.fetchUserInfo(completion: completion)
                } else {
                    self?.clearTokensAndHandleError()
                    completion(false)
                }
            } catch {
                self?.clearTokensAndHandleError()
                completion(false)
            }
        case .failure:
            self?.clearTokensAndHandleError()
            completion(false)
        }
    }
}

이 autoLogin 함수는 앱 시작 시 호출되어 자동 로그인을 시도합니다. 전체적인 과정으로는

  1. 저장된 리프레시 토큰을 확인합니다.
  2. 리프레시 토큰을 사용해 새로운 액세스 토큰을 요청합니다.
  3. 성공하면 새 토큰을 저장하고 사용자 정보를 가져옵니다.
  4. 실패하면 토큰을 삭제하고 로그인 상태를 업데이트합니다.

또한 AuthInterceptor를 통해 모든 요청에 자동으로 액세스 토큰이 포함되므로, 다른 서비스에서는 별도로 토큰을 처리할 필요가 없습니다. 예를 들어:

func updateUserProfile() {
    provider.request(.userProfile) { result in
        switch result {
        case .success(let response):
        //성공시
        case .failure(let error):
        }
    }
}

이 fetchUserProfile 함수에서는 별도의 토큰 처리 로직이 없지만, AuthInterceptor에 의해 자동으로 액세스 토큰이 요청 헤더에 포함됩니다.