RxSwift续订身份验证令牌

时间:2019-04-17 18:33:15

标签: swift oauth rx-swift moya

为什么我要提出一个使用Moya和RxSwift的解决方案,该解决方案可以更新身份验证令牌并重试请求。

问题是我同时有多个请求,因此可以说有10个请求在身份验证令牌到期时触发,我将尝试在所有令牌上续签该令牌,并且第一个续订后其他的将失败,因为它们使用了错误的令牌进行续签。

我想做的只是建立(也许)一个请求队列,然后重试这些请求。不确定这是否是最佳方案。

这是我到目前为止所拥有的:

final class NetworkOnlineProvider {

    fileprivate let database = DatabaseClient(database: DatabaseRealm()).database
    fileprivate let provider: MoyaProvider<NetworkAPI>

    init(endpointClosure: @escaping MoyaProvider<NetworkAPI>.EndpointClosure = MoyaProvider<NetworkAPI>.defaultEndpointMapping,
         requestClosure: @escaping MoyaProvider<NetworkAPI>.RequestClosure = MoyaProvider<NetworkAPI>.defaultRequestMapping,
         stubClosure: @escaping MoyaProvider<NetworkAPI>.StubClosure = MoyaProvider.neverStub,
         manager: Manager = MoyaProvider<NetworkAPI>.defaultAlamofireManager(),
         plugins: [PluginType] = [],
         trackInflights: Bool = false) {

        self.provider = MoyaProvider(endpointClosure: endpointClosure, requestClosure: requestClosure, stubClosure: stubClosure, manager: manager, plugins: plugins, trackInflights: trackInflights)
    }

    fileprivate func getJWTRenewRequest() -> Single<Response>? {
        if let token = JWTManager.sharedInstance.token {

            return provider.rx.request(.renew(token: token))
        }

        return nil
    }

    func tokenRequest() -> Single<String> {
        let errorSingle = Single<String>.create { single in
            single(.error(APIError.failure))

            return Disposables.create()
        }

        let emptyJWTSingle = Single<String>.create { single in
            single(.success(""))

            return Disposables.create()
        }

        // Return if no token found
        guard let appToken = JWTManager.sharedInstance.getJWT() else {
            return refreshToken() ?? emptyJWTSingle
        }

        // If we have a valid token, just return it
        if !appToken.hasTokenExpired {
            return Single<String>.create { single in
                single(.success(appToken.token))

                return Disposables.create()
            }
        }

        // Token has expired
        let newTokenRequest = refreshToken()
        return newTokenRequest ?? errorSingle
    }

    func refreshToken() -> Single<String>? {
        return getJWTRenewRequest()?
            .debug("Renewing JWT")
            .filterSuccessfulStatusCodes()
            .map { (response: Response) -> (token: String, expiration: Double) in
                guard let json = try? JSON(data: response.data) else { throw RxError.unknown }

                let success = json["success"]

                guard
                    let jwt = success["jwt"].string,
                    let jwt_expiration = success["jwt_expiration"].double,
                    let valid_login = success["valid_login"].bool, valid_login
                    else { throw RxError.unknown }

                return (token: jwt, expiration: jwt_expiration)
            }
            .do(onSuccess: { (token: String, expiration: Double) in
                JWTManager.sharedInstance.save(token: JWT(token: token, expiration: String(expiration)))
            })
            .map { (token: String, expiration: Double) in
                return token
            }
            .catchError { e -> Single<String> in
                print("Failed to Renew JWT")
                JWTManager.sharedInstance.delete()

                UIApplication.shared.appDelegate.cleanPreviousContext(jwt: true)
                let loginVC = UIStoryboard(storyboard: .login).instantiateViewController(vc: LoginViewController.self)
                UIApplication.shared.appDelegate.window?.setRootViewController(UINavigationController(rootViewController: loginVC))

                throw e
            }
    }

    func request(_ target: NetworkAPI) -> Single<Response> {
        let actualRequest = provider.rx.request(target)

        if target.isAuthenticatedCall {
            return tokenRequest().flatMap { _ in
                actualRequest
            }
        }


        return actualRequest
    }
}

1 个答案:

答案 0 :(得分:1)

解决方案在这里:RxSwift and Retrying a Network Request Despite Having an Invalid Token

关键是使用flatMapFirst,因此您只对第一个401发出一个请求,而在该请求进行期间忽略其他401。

与本文相关的要点包括证明其有效的单元测试。