RxSwift 更新身份验证令牌

Posted

技术标签:

【中文标题】RxSwift 更新身份验证令牌【英文标题】:RxSwift renew Authentication Token 【发布时间】:2019-04-17 18:33:14 【问题描述】:

我正在尝试使用 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】:

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

关键是使用 flatMapFirst,这样您只需对第一个 401 发出一个请求,而在该请求进行中时忽略其他 401。

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

【讨论】:

以上是关于RxSwift 更新身份验证令牌的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 2.1.7 - 用户通过身份验证后更新安全令牌设置特定角色

RXSwift 用户输入错误和继续

令牌在尝试刷新后被取消身份验证

无需身份验证令牌即可访问 Google 电子表格 API

如何在 MVVM-C RxSwift 中实现 firebase 身份验证

Angularjs 和 slim 框架 JWT 身份验证和令牌刷新流程