티스토리 뷰
안녕하세요. 리더가 JWT 인증 관련 샘플 앱을 만들어보라고 해서 만드는 김에 블로그에 정리하려고 합니다.
처음에는 아래의 프로토콜에 포함된 adapt와 retry 메서드를 이용해 jwt 인증을 구현하려고 했습니다.
protocol RequestInterceptor : RequestAdapter, RequestRetrier
func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
func retry(_ request: Request, for session: Alamofire.Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
api 요청할때 해당 프로토콜을 채택한 클래스를 interceptor 파라미터로 전달했지만 adapt 메서드는 호출이 되는데 401 에러가 떨어져도 retry 메서드가 호출이 안돼서 찾아보다가 Alamofire 5.2 버전에 OAuth 인증과 관련된 AuthenticationCredential과 Authenticator 프로토콜이 추가된 걸 발견하고 이를 통해 jwt를 구현했습니다.
AuthenticationCredential 프로토콜을 채택한 OAuthCredential 구조체와 Authenticator 프로토콜을 채택한 OAuthAuthenticator 클래스를 통해 AuthenticationInterceptor를 만들어 api요청시에 interceptor 파라미터로 전달하면 요청이 실패했을 때 OAuthenticator에 구현된 메서드를 자동으로 호출해줍니다.
OAuthCredential에서 채택한 Authenticator 프로토콜은 인증과 관련된 정보를 담고 있는 구조체인데
공식 문서에는 저렇게 나와있지만 저는 통신하는 서버에서 토큰의 expiration을 주지 않는 등 구현방식이 조금 달라서 필요한 정보만 담았습니다. requiresRefresh는 필수라 선언했지만 기본값을 주고 실제 사용은 하지 않았습니다.
OAuthAuthenticator 클래스가 채택한 Authenticator 프로토콜에는 아래와 같이 4개의 메서드가 정의되어 있습니다.
func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest)
func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool
func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool
func refresh(_ credential: OAuthCredential, for session: Session, completion: @escaping (Result<OAuthCredential, Error>) -> Void)
해당 메서드들의 간단한 플로우 입니다.
순서대로 살펴보겠습니다.
apply 메서드는 api요청 시 Authenticator를 구현한 클래스를 interceptor로 지정하였다면 요청의 성공 실패와 상관없이 요청하기 전 호출되어 HTTPHeader에 jwt토큰을 추가하는 역할을 합니다.
accessToken은 HTTPHeader.authorization(bearerToken:) 통해 request header에 추가했지만 refreshToken은 구현되어있지 않아서 addValue를 통해 추가했습니다.
didRequest 메서드는 api 요청 후 응답의 상태 코드가 validate를 통과하지 못하면 호출되는데, true를 리턴하면 프로세스가 계속 진행되고 false를 리턴하면 다음 프로세스를 진행하지 않습니다.
저는 accessToken이 만료되면 RefreshToken을 통해 갱신하는 게 목적이기 때문에 response.statusCode가 401(인증 실패) 일 경우에 true를 리턴해서 프로세스를 계속 진행하게 했습니다.
isRequest는 사실 무슨 의미가 있는지 잘 모르겠어서 공식 예제에 있는 코드를 그대로 사용하였습니다.
credential에 있는 accessToken과 요청했던 urlRequest의 header에서 Authorization 필드의 값과 비교하여 Bool값을 리턴합니다.
true를 리턴하면 refresh 메서드를 호출하고 false를 리턴하면 apply 메서드를 호출해서 프로세스를 다시 진행합니다.
제 생각에는 false를 리턴하면 Request header에 Token을 추가하는 apply 메서드부터 다시 호출하는 것으로 보아, Request에 대한 응답이 온 뒤에 credential의 accessToken 값이 변경되지 않았는지 검사하는 그런 로직이 아닐까 싶습니다.
마지막으로 호출되는 refresh 메서드입니다.
저는 apply 메서드에서 header에 accessToken과 refresh-token 필드를 둘 다 설정해줬었고 token을 리프레시하는 api를 호출하면 서버에서 refresh-token 필드의 값을 통해 갱신된 accessToken을 전달해주는 형태입니다.
응답에 대한 처리는 프로젝트의 구조에 따라 다를 수 있기 때문에 refresh 메서드만을 살펴보면 토큰 갱신 후 completion을 호출해야 하는데, success와 갱신된 token이 추가된 credential을 연관 값으로 전달할 경우 기존에 실패했던 request를 다시 호출합니다.
이때 apply 메서드의 credential에는 completion(. success(credential))에서 전달해줬던 credential이 인자로 전달됩니다.
failure의 경우에는 error를 연관 값으로 completion을 호출하며 종료됩니다.
샘플용으로 만든 앱이라 전체적인 동작 방식 위주로 간단히 알아봤는데 혹시 잘못된 점이 있다면 댓글로 지적 부탁드립니다.
'iOS' 카테고리의 다른 글
[iOS] NSCache를 이용한 이미지 캐싱 (0) | 2021.07.12 |
---|---|
iOS 아키텍처 패턴 MVVM (0) | 2021.06.22 |
[iOS] 비동기 API 호출 UnitTest 하는법 (0) | 2021.06.21 |
iOS 아키텍처 패턴 VIPER (0) | 2021.05.14 |
[iOS] ObjectMapper 와 codable 차이 (0) | 2020.08.08 |
- Total
- Today
- Yesterday
- ios cache
- ios 이미지캐싱
- ios 디자인패턴
- 연산프로퍼티
- UserDefault
- @propertyWrapper
- rxswift 없이
- swift 캐싱
- MVVM패턴
- api호출 unittest
- inputoutput패턴
- swift cache
- 앱개발 디자인패턴
- UserDefaults
- ios 유닛테스트
- 아키텍처패턴
- 프로퍼티래퍼
- ios expectation
- rxswift안쓰고
- swift mvvm
- wrappedValue
- ios 패턴
- NSCache
- ios 캐시
- ios image caching
- UserDefaultKey
- ios memory
- SWIFT
- 비동기 유닛테스트
- ios 캐시메모리
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |