使用 Moya 刷新身份验证令牌

Posted

技术标签:

【中文标题】使用 Moya 刷新身份验证令牌【英文标题】:Refreshing auth token with Moya 【发布时间】:2019-02-28 23:57:37 【问题描述】:

我正在使用 Moya 与我的 API 进行通信。对于我的许多端点,我要求对用户进行身份验证(即承载令牌基于 Authorization 标头)。

在 Moya 文档here 中,我找到了如何包含授权标头以及不记名令牌。

但是,我现在需要实现身份验证令牌刷新,我不确定如何执行此操作。

我在 Moya 的 Github 上找到了 this thread,答案看起来可能会有所帮助,但我不知道将代码放在哪里。答案的代码如下所示:

// (Endpoint<Target>, NSURLRequest -> Void) -> Void
static func endpointResolver<T>() -> MoyaProvider<T>.RequestClosure where T: TargetType 
    return  (endpoint, closure) in
        let request = endpoint.urlRequest!
        request.httpShouldHandleCookies = false

        if (tokenIsOK) 
            // Token is valid, so just resume the request and let AccessTokenPlugin set the Authentication header
            closure(.success(request))
            return
        
        // authenticationProvider is a MoyaProvider<Authentication> for example
        authenticationProvider.request(.refreshToken(params))  result in
            switch result 
                case .success(let response):
                    self.token = response.mapJSON()["token"]
                    closure(.success(request)) // This line will "resume" the actual request, and then you can use AccessTokenPlugin to set the Authentication header
                case .failure(let error):
                    closure(.failure(error)) //something went terrible wrong! Request will not be performed
            
        
    

这是我的 Moya 提供者的课程:

import Foundation
import Moya

enum ApiService 
    case signIn(email: String, password: String)
    case like(id: Int, type: String)


extension ApiService: TargetType, AccessTokenAuthorizable 
    var authorizationType: AuthorizationType 
        switch self 
        case .signIn(_, _):
            return .basic
        case .like(_, _):
            return .bearer
        
    

    var baseURL: URL 
        return URL(string: Constants.apiUrl)!
    

    var path: String 
        switch self 
            case .signIn(_, _):
                return "user/signin"
            case .like(_, _):
                return "message/like"
        
    

    var method: Moya.Method 
        switch self 
            case .signIn, .like:
                return .post
        
    

    var task: Task 
        switch self 
            case let .signIn(email, password):
                return .requestParameters(parameters: ["email": email, "password": password], encoding: JSONEncoding.default)
            case let .like(id, type):
                return .requestParameters(parameters: ["messageId": id, "type": type], encoding: JSONEncoding.default)
        
    

    var sampleData: Data 
        return Data()
    

    var headers: [String: String]? 
        return ["Content-type": "application/json"]
    


private extension String 
    var urlEscaped: String 
        return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    

    var utf8Encoded: Data 
        return data(using: .utf8)!
    


我应该将答案的代码放在我的代码中的什么位置?我错过了什么吗?

【问题讨论】:

【参考方案1】:

实际上,这个例子有点老了。所以这是一个新的:

extension MoyaProvider 
    convenience init(handleRefreshToken: Bool) 
        if handleRefreshToken 
            self.init(requestClosure: MoyaProvider.endpointResolver())
         else 
            self.init()
        
    

    static func endpointResolver() -> MoyaProvider<Target>.RequestClosure 
        return  (endpoint, closure) in
            //Getting the original request
            let request = try! endpoint.urlRequest()

            //assume you have saved the existing token somewhere                
            if (#tokenIsNotExpired#)                    
                // Token is valid, so just resume the original request
                closure(.success(request))
                return
            

            //Do a request to refresh the authtoken based on refreshToken
            authenticationProvider.request(.refreshToken(params))  result in
                switch result 
                case .success(let response):
                    let token = response.mapJSON()["token"]
                    let newRefreshToken = response.mapJSON()["refreshToken"]
                    //overwrite your old token with the new token
                    //overwrite your old refreshToken with the new refresh token

                    closure(.success(request)) // This line will "resume" the actual request, and then you can use AccessTokenPlugin to set the Authentication header
                case .failure(let error):
                    closure(.failure(error)) //something went terrible wrong! Request will not be performed
                
            
    

用法:

public var provider: MoyaProvider<SomeTargetType> = MoyaProvider(handleRefreshToken: true)

provider.request(...)

【讨论】:

authenticationProvider.request 实例来自哪里? 如果发出多个请求会怎样?每次请求都会多次调用refreshToken api吗? @ugur 您应该检查令牌是否已过期,然后再进行刷新,如答案中所述 好的,但假设令牌已过期并且同时发出请求。因此,在这种情况下,将为每个请求触发刷新令牌,并且假设每个刷新令牌都使前一个刷新令牌过期,实际请求可能会使用错误的令牌恢复。 Alamofire RequestRetrier 通过在列表中收集请求并在刷新令牌响应后恢复所有请求来解决此问题。我是 Moya 的新手,我错过了什么吗? @session过期如何使用?

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

如果身份验证令牌在帐户身份验证器中过期,则使用刷新令牌

如何使用 Firebase 刷新令牌保持用户身份验证?

使用访问令牌和刷新令牌保持身份验证

如何在okhttp3身份验证器中使用具有异步请求的刷新令牌添加身份验证

在基于令牌的身份验证中使用刷新令牌是不是安全?

我是不是正确理解用于身份验证的访问和刷新令牌技术?