Лучшее решение для обновления токена автоматически с помощью 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​​strong > (в этом случае RetryRequestOperation является подклассом RKManagedObjectRequestOperation).

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

Ответ 1

Ваше текущее решение работает, и у вас есть код для него, для его достижения может быть разумный объем кода, но этот подход имеет свои достоинства.

Использование подхода на основе NSURLAuthenticationChallenge означает подклассирование на другом уровне и увеличение каждой созданной операции с помощью setWillSendRequestForAuthenticationChallengeBlock:. В общем, это был бы лучший подход, поскольку одна операция была бы использована для выполнения всей операции, а не для ее копирования и обновления данных, а поддержка auth операции выполняла бы задачу auth вместо обработчика завершения операции. Это должно быть меньше кода для поддержки, но этот код, скорее всего, будет понят меньшим количеством людей (или потребуется больше времени для понимания большинством), поэтому сторона обслуживания вещей, вероятно, балансирует на всех.

Если вы ищете элегантность, подумайте об изменении, но, учитывая, что у вас уже есть рабочее решение, в противном случае выигрыш мало.

Ответ 2

Я искал ответ на эту проблему, и Создатель AFNetworking "Matt" предлагает это:

лучшее решение, которое я нашел для этого, - использовать зависимые NSOperations, чтобы проверить действительный токен с истекшим сроком действия до исходящий запрос разрешен. В этот момент это до разработчик, чтобы определить лучший курс действий для обновления маркер или приобретение нового в первую очередь.