【发布时间】:2014-08-18 07:11:58
【问题描述】:
一旦您的用户登录,您将获得一个令牌(digest 或 oauth),您将其设置到您的 HTTP 授权标头中,并授权您访问您的网络服务。 如果您将用户名、密码和此令牌存储在手机的某个位置(在用户默认值中,或者最好在钥匙串中),那么每次应用程序重新启动时,您的用户都会自动登录。
但是如果您的令牌过期怎么办?然后你“只需要”要求一个新的令牌,如果用户没有更改他的密码,那么他应该再次自动登录。
实现此令牌刷新操作的一种方法是继承 AFHTTPRequestOperation 并处理 401 Unauthorized HTTP 状态代码以请求新令牌。颁发新令牌后,您可以再次调用失败的操作,现在应该会成功。
那么您必须注册这个类,以便每个 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、授权类型(摘要、誓言等)以及您使用的框架类型通过 AFNetworking(例如 RestKit)。
这是非常有效的,并且已经证明使用与 CoreData 绑定的 RestKit 可以很好地处理摘要和 oauth 授权(在这种情况下,RetryRequestOperation 是 RKManagedObjectRequestOperation 的子类)。
我现在的问题是:这是刷新令牌的最佳方式吗?
我实际上想知道是否可以使用NSURLAuthenticationChallenge 以更优雅的方式解决这种情况。
【问题讨论】:
-
我同意 Wain 的观点,即这个实现更易于阅读和理解。不过,有一个小批评:AFNetworking 保证将调用成功或失败块。到达
[SessionManager logout]的代码路径违反了这个保证。 -
感谢@AaronBrager,关于您的简洁评论,示例代码现已修复。
-
伙计,我也在用RestKit+CoreData,我只想说,你的方法太对了。谢谢。
-
@Phil 你能告诉我如何使用方法覆盖创建子类的对象吗?我的意思是,如果我使用的是 RestKit,我会创建一个
RKManagedObjectRequestOperation和- (id)appropriateObjectRequestOperationWithObject:(id)object,但我需要它作为我的子类的一个实例。所以在这种情况下,我无法实现要调用的重写方法。 -
@Phil 抱歉这个愚蠢的问题。我找到了一个方法
- (BOOL)registerRequestOperationClass:(Class)operationClass。您的解决方案完美运行,再次感谢您。
标签: ios authentication oauth restkit afnetworking