2014-06-27 30 views
10

Una volta effettuato l'accesso, si ottiene un token (digest o oauth) impostato nell'intestazione dell'autorizzazione HTTP e che consente l'accesso al servizio Web. Se si memorizzano il nome dell'utente, la password e questo token da qualche parte sul telefono (nelle impostazioni predefinite dell'utente, o preferibilmente nel portachiavi), l'utente viene automaticamente registrato ogni volta che si riavvia l'applicazione.La migliore soluzione per aggiornare automaticamente token con AFNetworking?

E se il token scade? Quindi "semplicemente" è necessario chiedere a un nuovo token e se l'utente non ha cambiato la sua password, allora dovrebbe essere registrato nuovamente automaticamente.

Un modo per implementare questo token operazione rinfrescante è quello di creare una sottoclasse AFHTTPRequestOperation e prendersi cura di 401 codice di stato HTTP non autorizzato, al fine di chiedere un nuovo token. Quando viene emesso il nuovo token, è possibile richiamare ancora una volta l'operazione non riuscita che dovrebbe ora riuscire.

Quindi è necessario registrare questa classe in modo che ogni richiesta di AFNetworking (getPath, postPath, ...) utilizzi questa classe.

[httpClient registerHTTPOperationClass:[RetryRequestOperation class]] 

Ecco un esempio di una tale classe:

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]; 
} 

prega, essere consapevoli del fatto che questo codice non funziona così com'è, in quanto si basa sul codice esterno che dipende dal vostro web API, il tipo di autorizzazione (digest, giuramento, ...) e anche il tipo di framework che si utilizza su AFNetworking (RestKit ad esempio).

Questo è molto efficiente ed è dimostrato di funzionare bene sia digerire e autorizzazione OAuth utilizzando RestKit legato al CoreData (in questo caso RetryRequestOperation è una sottoclasse di RKManagedObjectRequestOperation).

La mia domanda ora è: è questo il modo migliore per aggiornare un token? In realtà mi chiedo se sia possibile utilizzare NSURLAuthenticationChallenge per risolvere questa situazione in modo più elegante.

+2

Sono d'accordo con Wain sul fatto che questa implementazione sia più semplice da leggere e comprendere. Una piccola critica, però: AFNetworking garantisce che venga chiamato il blocco di successo o di fallimento. Il percorso del codice che raggiunge '[SessionManager logout]' viola questa garanzia. –

+0

Grazie a @AaronBrager, il codice di esempio è stato corretto per quanto riguarda la tua osservazione accurata. – Phil

+0

Man, sto usando anche RestKit + CoreData e tutto quello che voglio dire è che il tuo metodo è giusto. Grazie. – kokoko

risposta

0

La tua soluzione corrente funziona e tu hai il codice per questo, ci potrebbe essere una quantità ragionevole di codice per raggiungerlo, ma l'approccio ha dei meriti.

Utilizzare un approccio basato su NSURLAuthenticationChallenge sottoclasse a un livello diverso e aumentare ciascuna operazione creata con setWillSendRequestForAuthenticationChallengeBlock:. In generale, questo sarebbe un approccio migliore in quanto una singola operazione sarebbe utilizzata per eseguire l'intera operazione piuttosto che doverla copiare e aggiornare i dettagli, e il supporto di autenticazione delle operazioni eseguirà l'attività di autenticazione anziché il gestore del completamento dell'operazione. Questo dovrebbe essere meno codice da mantenere, ma quel codice sarà probabilmente compreso da meno persone (o richiederà più tempo per essere capito dalla maggior parte) quindi il lato manutenzione delle cose probabilmente si bilancia su tutto.

Se stai cercando l'eleganza, allora considera di cambiare, ma dato che hai già una soluzione funzionante c'è poco guadagno altrimenti.

+1

Puoi dare qualche pseudo-codice? Ho provato a chiamare '[AFHTTPRequestOperation setWillSendRequestForAuthenticationChallengeBlock:]' ma il blocco non viene mai chiamato anche se la richiesta riceve un codice di stato 401. O c'è qualche configurazione specifica da fare su 'AFHTTPClient'? – Phil

+0

Quando impostato verrà chiamato direttamente da 'connection: willSendRequestForAuthenticationChallenge:' metodo delegate. – Wain

0

ero alla ricerca di una risposta a questo problema e "Matt", the creator of AFNetworking, suggest this:

la soluzione migliore che ho trovato per affrontare questo è quello di utilizzare dipendenti NSOperations per verificare la presenza di un token non-scaduto valida prima di ogni richiesta in uscita è consentita per passare attraverso. A quel punto, è lo lo sviluppatore a determinare il miglior modo di agire per rinfrescare il token o acquisirne uno nuovo in primo luogo.

Problemi correlati