Лучшее решение для автоматического обновления токена с AFNetworking?

Как только ваш пользователь вошел в систему, вы получаете токен ( дайджест или oauth ), который вы устанавливаете в заголовке HTTP-авторизации и который дает вам разрешение на доступ к вашему веб-сервису. , Если вы храните имя пользователя, пароль и этот токен где-то на телефоне (по умолчанию или предпочтительно в цепочке для ключей), то ваш пользователь автоматически регистрируется при каждом перезапуске приложения.

Но что, если ваш токен истекает ? Затем вам «просто» нужно запросить новый токен , и если пользователь не изменил свой пароль, то он должен снова войти в систему автоматически .

Один из способов реализовать эту операцию обновления токена - это создать подкласс AFHTTPRequestOperation и позаботиться о 401 неавторизованном коде 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];
}

Пожалуйста, имейте в виду, что этот код не работает как есть, так как он опирается на внешний код, который зависит от вашего веб-API, тип авторизации (дайджест, клятва, ...), а также тип фреймворка, который вы используете для AFNetworking (RestKit, например).

Это довольно эффективно и доказало, что хорошо работает как с дайджестом, так и с oauth-авторизацией с использованием RestKit , привязанного к CoreData (в этом случае RetryRequestOperation является подклассом RKManagedObjectRequestOperation).

Мой вопрос сейчас: это лучший способ обновить токен? Мне действительно интересно, можно ли использовать NSURLAuthenticationChallenge, чтобы разрешить эту ситуацию более элегантно.

10
задан Phil 29 June 2014 в 08:39
поделиться