iOS:使用NSURLSession进行身份验证和令牌过期

时间:2014-03-01 00:05:45

标签: ios authentication authorization nsurlsession

我的应用程序具有相当简单的授权/身份验证,需要一些有关如何处理授权生命周期的帮助。这是它的主旨:

  1. 用户输入凭据进行登录,服务器使用令牌进行响应并存储在钥匙串中
  2. 该令牌用于每个后续URL请求(REST调用)的“授权”标头
  3. 令牌最终到期,服务器将响应401状态码
  4. 此时用户需要通过登录屏幕重新进行身份验证,以便我们获取新令牌并重试请求
  5. 这里有一些代码,代表了我现在能想到的最好的代码。

    static NSString * _authorizationToken;
    @implementation MyRestfulModel
    + (id) sharedRestfulModel
    {
        // singleton model shared across view controllers
        static MyRestfulModel * _sharedModel = nil;
        @synchronized(self) {
            if (_sharedModel == nil) 
                _sharedModel = [[self init] alloc];
        }
        return _sharedModel;
    }
    
    + (NSString *) authorizationToken
    {
        if (!_authorizationToken)
            _authorizationToken = @"";
        return _authorizationToken;
    }
    
    + (void) setAuthorizationToken: (NSString *token)
    {
        _authorizationToken = token;
    }
    
    -(void) doSomeRestfulCall: (NSString *) restURL
            completionHandler: (void (^)(NSData * data)) callback
    {
        // construct url path and request
        NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:restURL];
        [request setValue:MyRestfulModel.authorizationToken forHTTPHeaderField:@"Authorization"];
        [[[NSURLSession sharedSession] dataTaskWithRequest: request
                                         completionHandler:^(NSData *data, NSURLResponse * response, NSError * error) {
    
                        NSHTTPResponse * httpResponse = (NSHTTPResponse *) response;
                        if([httpResponse statusCode] == 401) {  // WHAT TO DO HERE ?
    
                            dispatch_sync(dispatch_get_main_queue(), ^{
                                MyAppDelegate * delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
                                [delegate showLoginViewController callback:^(NSString * username, NSString * newToken) {
                                    // recreate the NSURLSession and NSURLConfiguration with the new token and retry
                                    MyRestfulModel.authenticationToken = token;
                                    [self doSomeRestfulCall:callback];
                                }];
                            }
                            return;
                        } else {
                            callback(data);
                        }
        }] resume];
    }
    

    我希望这样做,以便视图控制器永远不必担心由于令牌过期而重试呼叫,所有可怕的会话处理和身份验证都可以由一个对象完成。

    我正在考虑尝试使用NSURLSessionDelegate,但我无法弄清楚w.r.t中的didReceiveChallenge部分。弹出登录视图控制器。我正在考虑的另一个选项是添加一个类似于完成处理程序的“retryHandler”,它将在用户重新进行身份验证后调用。

1 个答案:

答案 0 :(得分:0)

我想我找到了一个很好的解决方案。以“doSomeRestfulCall”方法为例。

-(void) doSomeRestfullCall: (NSString *) restURL
         completionHandler: (void (^)(NSData * data)) callback
{
    // construct url path and request
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:restURL];
    [request setValue:MyRestfulModel.authorizationToken forHTTPHeaderField:@"Authorization"];
    [[[NSURLSession sharedSession] dataTaskWithRequest: request
                                     completionHandler:^(NSData *data, NSURLResponse * response, NSError * error) {
                   NSHTTPResponse * httpResponse = (NSHTTPResponse *) response;
                   if([httpResponse statusCode] == 401) {
                        [LoginViewController showWithCompletion:^(NSString *username, NSString *token) {
                            NSLog(@"Retrying request after user reauthenticated");
                            MyRestfulModel.authorizationToken = token;
                            [self doSomeRestfulCall:restURL completionHandler:callback];
                        }];
                        return;
                   } else {
                       callback(data);
                   }
               }] resume];
}

然后登录视图控制器就会发生很多魔法。 (如果您不想要自定义登录控制器,也可以使用警报视图,请查看此帖子:http://nscookbook.com/2013/04/ios-programming-recipe-22-simplify-uialertview-with-blocks/

@interface LoginViewController
@property (copy) void(^completionBlock)(NSString * username, NSString * tokenCredentials);
@end

@implementation LoginViewController

+ (void) showWithCompletion: (void (^)(NSString * username, NSString * tokenCredentials))completion
{
    AppDelegate * appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    UIViewController * rootController = appDelegate.window.rootViewController;
    UIStoryboard * storyboard = rootController.storyboard;

    LoginViewController * controller = [storyboard instantiateViewControllerWithIdentifier:@"Login"];
    controller.completionBlock = completion;

    controller.delegate = wrapper;
    controller.modalPresentationStyle = UIModalPresentationFormSheet;
    [rootController presentViewController:controller animated:YES completion:nil];
}


//
// <code for contacting your authentication service>
//


-(void) loginSuccessful: (NSString *) username withTokenCredentials:(NSString *)token
{
    // <code to store username and token in the keychain>

    if (self.completionBlock)
        self.completionBlock(username, token);
}