最近运用Moya突然碰到token改写的问题,一直没有放在心上,最近有时间就好好的弄了下。
曾经对于服务端Token的这个问题都没有怎样关心过,顶多处理一下过期后要求从头登录而已,就算要做续期服务端或许就直接帮咱们做掉了。这次暴露出来的这个问题整了好久才算有一个比较满意的效果。
遇到的问题
服务端界说了token的有效期,差不多在2个小时。token过期后需求前端自行调用改写token接口。
一开始其实没有放在心上,想着服务端报错后就进行token的改写就行了,没什么问题。
效果还是太年轻啊,一个并发直接给整懵了。
怎样处理
开始实践走的弯路
这种大面积都会碰到的问题应该有许多资料的吧。
公然参照这个小哥哥的写法 [Moya 的 RxSwift 扩展之后台无感改写token] 大致理解了下,感觉上逻辑也是行得通,无非便是并发的时分多处理一下就行。
流程大概是这样:
央求
-> 过滤不需求token的央求
-> 拿到照应效果后处理是否需求改写token
-> 重发上一次央求
效果然到处理并发的时分问题就来了,这个方法是在拿到照应效果后才会处理,在异步一同调用多个接口的时分会构成先后顺序不一致,且token改写时会紊乱,毕竟导致改写token时构成token不合法。
毕竟处理
在服务端的配合下将token过期时间调整到很短的情况下,多次测验后毕竟抛弃上面的计划。
通过一段时间的资料查找,毕竟找到相关可参阅的计划 [Alamofire – 运用拦截器高雅的对接口进行授权] 、 [Moya结构浅析]、[Swift Moya改写过期Token]
毕竟的核心处理方法是运用Alamofire
中的session
增加授权拦截器AuthenticationInterceptor
流程大致是这样:
选定指定的Provider实行央求
->校验凭证本地设置的有效期
->处理401过期情况
->正常处理央求
根据这些资料,基本能够承认运用Alamofire
中的AuthenticationInterceptor
即可完结我想要的作用。
因为我运用了MultiTarget
单个Provider
统一管理一切央求,所以在毕竟的完结上还是有点不同
首要界说一个用于存放token的授权凭证 OAuthCredential
struct OAuthCredential: AuthenticationCredential {
var accessToken: String = Global.shared.token {
didSet {
Global.shared.token = accessToken
}
}
var refreshToken: String = Global.shared.refreshToken {
didSet {
Global.shared.refreshToken = refreshToken
}
}
var userID: String = ""
var expiration: Date = Date(timeIntervalSinceNow: OAuthAuthenticator.expirationDuration)
// 这儿咱们在有效期行将过期的 `OAuthAuthenticator.expirationDuration - OAuthAuthenticator.expirationDuration * 0.2` 秒回来需求改写
var requiresRefresh: Bool { Date(timeIntervalSinceNow: OAuthAuthenticator.expirationDuration * 0.2) > expiration }
}
完结授权拦截器
class OAuthAuthenticator: Authenticator {
/// token 过期时间
static let expirationDuration: TimeInterval = 60 * 100
/// 增加header
func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
urlRequest.headers.add(.authorization(bearerToken: Global.shared.token))
}
/// 完结改写流程
func refresh(_ credential: OAuthCredential,
for session: Session,
completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
logger.debug("需求改写token!!!")
MoyaProvider<AuthApi>(plugins: [ NetworkLoggerPlugin()]).request(.refreshToken) { result in
switch result {
case .success(let response):
logger.debug("response = \(response.statusCode)")
guard let dic = dataToDictionary(data: response.data) else{
completion(.failure(YHError.other(code: -999, msg: "解析过错")))
return
}
guard let model = ResponseBaseModel<RefreshToken>(JSON: dic) else {
completion(.failure(YHError.other(code: -999, msg: "解析过错")))
return
}
guard model.code == 0 else {
let hud = HUD.show(message: "请从头登录")
hud?.pinned = true
Global.logout()
completion(.failure(YHError.other(code: -999, msg: "请从头登录")))
return
}
Global.shared.refreshToken = model.data?.refresh_token ?? ""
Global.shared.token = model.data?.token ?? ""
completion(.success(OAuthCredential(accessToken: model.data?.token ?? "",
refreshToken: model.data?.refresh_token ?? "",
userID: Global.shared.userInfo._id,
expiration: Date(timeIntervalSinceNow: OAuthAuthenticator.expirationDuration))))
case .failure(let error ):
logger.debug("error = \(error)")
completion(.failure(error))
}
}
}
func didRequest(_ urlRequest: URLRequest,
with response: HTTPURLResponse,
failDueToAuthenticationError error: Error) -> Bool {
logger.debug("服务端token过期!!!!!!!!")
return response.statusCode == 401
}
func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
let bearerToken = HTTPHeader.authorization(bearerToken: credential.accessToken).value
return urlRequest.headers["Authorization"] == bearerToken
}
}
这儿有点需求注意,如果服务端的token先过期,回来401时Moya需求额定处理配置validationType
,否则无法触发AuthenticationInterceptor
的Retry
方法!
extension TargetType {
public var validationType: ValidationType {
switch self {
case .refreshToken:
return .none
default: return .successCodes
}
}
}
在处理是否需求授权凭证时,我这边是创立了2个不同的provider,用于创立有授权校验的session和无校验的session
public var netProvider = CustomProvider.createAuthProvider()
public var noAuthProvider = CustomProvider.createNoAuthProvider()
/// 创立需认证的provider
static func createAuthProvider() -> CustomProvider {
let networkLoggerPlugin = NetworkLoggerPlugin(configuration: .init(formatter: NetworkLoggerPlugin.Configuration.Formatter.init(entry: defaultEntryFormatter), output: reversedPrint, logOptions: .verbose))
let configuration = URLSessionConfiguration.default
configuration.headers = .default
let interceptor: AuthenticationInterceptor? = AuthenticationInterceptor(authenticator: OAuthAuthenticator(), credential: YHProvider.credential)
let customSession = Session(configuration: configuration, startRequestsImmediately: false, interceptor: interceptor)
return CustomProvider(endpointClosure: endpointClosure(),
requestClosure: requestClosure(),
stubClosure: MoyaProvider.neverStub,
callbackQueue: DispatchQueue.main,
session: customSession,
plugins: [networkLoggerPlugin, StorePlugin(), HeaderPlugin(), ErrorPlugin()],
trackInflights: false)
}
/// 创立无需认证的provider
static func createNoAuthProvider() -> CustomProvider {
let networkLoggerPlugin = NetworkLoggerPlugin(configuration: .init(formatter: NetworkLoggerPlugin.Configuration.Formatter.init(entry: defaultEntryFormatter), output: reversedPrint, logOptions: .verbose))
return CustomProvider(endpointClosure: endpointClosure(),
requestClosure: requestClosure(),
stubClosure: MoyaProvider.neverStub,
callbackQueue: DispatchQueue.main,
plugins: [networkLoggerPlugin, StorePlugin(), HeaderPlugin(), ErrorPlugin()],
trackInflights: false)
}