一旦您的用户登陆成功,你将得到一个 token (digest 或 oauth),并设置在您的 HTTP Authorization header 中,这将授权您访问您的 Web 服务。
如果您将用户的用户名、密码和此 token 存储在手机上(在用户默认设置或者最好是在 keychain 中),那么每次应用程序重新启动时,用户都会自动登陆。
但是,如果您的token过期怎么办? 然后你只需要请求一个新的 token,如果用户没有更改他的密码,那么他就应该再次被自动登录。
实现这个 token 刷新操作的一种方法是,子类化AFHTTPRequestOperation
并处理401未经授权的HTTP状态码,以便请求一个新的 token。当新的 token 发布时,您可以再次调用失败的操作,这应该会成功。
然后您必须注册此类,使每个 AFNetworking 请求 (getPath、postPath 等) 都使用这个类。
[httpClient registerHTTPOperationClass:[RetryRequestOperation class]]
以下是这样一个类的示例:
static NSInteger const kHTTPStatusCodeUnauthorized = 401;
@interface RetryRequestOperation ()
@property (nonatomic, assign) BOOL isRetrying;
@end
@implementation RetryRequestOperation
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *, id))success
failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure
{
__unsafe_unretained RetryRequestOperation *weakSelf = self;
[super setCompletionBlockWithSuccess:success failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// In case of a 401 error, an authentification with email/password is tried just once to renew the token.
// If it succeeds, then the opration is sent again.
// If it fails, then the failure operation block is called.
if(([operation.response statusCode] == kHTTPStatusCodeUnauthorized)
&& ![weakSelf isAuthenticateURL:operation.request.URL]
&& !weakSelf.isRetrying)
{
NSString *email;
NSString *password;
email = [SessionManager currentUserEmail];
password = [SessionManager currentUserPassword];
// Trying to authenticate again before relaunching unauthorized request.
[ServiceManager authenticateWithEmail:email password:password completion:^(NSError *logError) {
if (logError == nil) {
RetryRequestOperation *retryOperation;
// We are now authenticated again, the same request can be launched again.
retryOperation = [operation copy];
// Tell this is a retry. This ensures not to retry indefinitely if there is still an unauthorized error.
retryOperation.isRetrying = YES;
[retryOperation setCompletionBlockWithSuccess:success failure:failure];
// Enqueue the operation.
[ServiceManager enqueueObjectRequestOperation:retryOperation];
}
else
{
failure(operation, logError);
if([self httpCodeFromError:logError] == kHTTPStatusCodeUnauthorized)
{
// The authentication returns also an unauthorized error, user really seems not to be authorized anymore.
// Maybe his password has changed?
// Then user is definitely logged out to be redirected to the login view.
[SessionManager logout];
}
}
}];
}
else
{
failure(operation, error);
}
}];
}
- (BOOL)isAuthenticateURL:(NSURL *)url
{
// The path depends on your implementation, can be "auth", "oauth/token", ...
return [url.path hasSuffix:kAuthenticatePath];
}
- (NSInteger)httpCodeFromError:(NSError *)error
{
// How you get the HTTP status code depends on your implementation.
return error.userInfo[kHTTPStatusCodeKey];
}
请注意,这段代码并不能直接运行,它依赖于外部代码,该代码又依赖于您的 Web API、授权方式(摘要、Oauth 等)以及您在 AFNetworking 上使用的框架类型(例如 RestKit)。
这种方法非常有效,并且已经被证明可以与使用 RestKit 绑定到 CoreData 的摘要和 Oauth 授权一起很好地工作(在这种情况下,RetryRequestOperation 是 RKManagedObjectRequestOperation
的子类)。
我的问题是:这是刷新令牌的最佳方法吗?我想知道是否可以使用 NSURLAuthenticationChallenge
以更优雅的方式解决此情况。
[SessionManager logout]
的代码路径违反了这个保证。 - Aaron Brager- (id)appropriateObjectRequestOperationWithObject:(id)object
创建一个RKManagedObjectRequestOperation
,但我需要它成为我的子类的实例。所以在这种情况下,我无法调用我的重写方法。 - kokoko- (BOOL)registerRequestOperationClass:(Class)operationClass
。你的解决方案完美地解决了我的问题,再次感谢你。 - kokoko