티스토리 툴바


개발/objective-c 2011/12/15 10:56

iOS 트위터 OAuth 인증 연동. - UIWebViewDelegate Protocol 이용

이전 글에 이어서 OAuth방식의 트위터 연동을 정리겸 글을 쓴다. 이전에 정리한 xAuth방식말고 OAuth방식이다.

정리를 하기 전에 우선 세가지 용어를 정리하면 다음과 같다. (티스토리 에디터가 개떡 같아서 리스트형을 사용할 수가 없어서 그냥 씀.)
    1. 서비스프로바이더 : 예를 들면 트위터, 포스퀘어
    2. 컨슈머 : 이 글을 읽는 개발자 당신이 개발하는 서비스
    3. 사용자 : 당신의 서비스(앱)을 사용하는 유저.

OAuith의 흐름을 그냥 말로 설명을 하면...
    1. 사용자가 컨슈머의 서비스에서 트위터 연동을 요청한다.
    2. 컨슈머는 트위터에 토큰을 요청한다.
    3. 프로파이더는 컨슈머를 확인하고 토큰을 리턴한다
    4. 컨슈머는 사용자를 토큰과 함께 프로바이더의 authrize url로 이동시킨다. 
    5. 사용자는 프로바이더 서비스 페이지에서 로그인후 컨슈머를 사용할지 결정한다.
    6. 사용하기로 결정을 하면 콜백URL로 oauth_verifier값을 함께 전달하여 이동시킨다.
    7. 컨슈머는 oauth_verifier와 함께 access_token을 요청한다.
    8. 프로바이더는 최종적으로 access_token, access_token_secret를 리턴한다.
    9. 컨슈머는 전달받은 값을 내부에 저자하여 해당 값으로 활용하여 연동을 한다.


여기서 아이폰앱을 개발하다 보면 사실 콜백URL을 만들수가 없다. 그래서 트위터에서는 콜백URL 을 지정하지 않으면 위의 6번 과정 대신에 페이지 리다이렉트 없이 핀코드를 보여준다. 핀코드를 사용자가 보고 앱내에서 입력을 받아서 access_token을 요청할수 있도록 한다. 아니면 xAuth방식으로 지원을 하면 된다.

하지만 내가 접해본 대부분의 앱은 위의 과정처럼 하지 않고 페이지가 리다이렉트 되면서 연동이 완료가 되는 앱이 대부분이다. 어떻게 그렇게 될까.. 생각을 하다 보니 UIWebView에서 페이지 이동시점을 알 수 있고, 그 URL을 알 수 있으면 oauth_verifier를 알아낼수 있지 않을까 하는 생각이 들어서 레퍼런스를 뒤져보니 webView:shouldStartLoadWithRequest:navigationType: 라는 프로토콜 메소드가 있다. 이를 활용을 하면 콜백을 감지하여 6번 이후의 과정을 실행할 수 있다.

위와 같은 아이디어를 기초로 해서 프로토타이핑한 코드는 아래와 같다. 우선 OAuth관련된 라이브러리는 oauth.net에 소개되어 있는 OAuthConsumer를 활용하려는데 이게 기본적으로는 Mac용이라서 아이폰용으로는 바로 사용을 할 수가 없어서 검색하여 적용을 했는데, 나중에 보니  누가 아이폰용으로 만들어 놓은것도 있었다. OAuthConsumer의 기본적인 사용방법은 http://code.google.com/p/oauthconsumer/wiki/UsingOAuthConsumer 를 참고하면 될듯하다. 실제로 프로토타이핑한 코드와 크게 다를바 없다.


프로토타이핑을 하기 전에 우선 트위터 개발자 사이트게 로그인을 해서 새로운 앱을 생성해야 한다. 생성할때 콜백URL을 적당한 값으로 입력을 하고 기록을 해 둬야 한다. 앱을  생성을 하면 consumer_key, consumer_secret를 생생해 준다.

우선 처음으로 위의 과정중 2번에 해당한다. 컨슈머를 생성하고 request_token을 요청한다.  요청이 성공을 하면 아래에 기술한 requestTokenTicket:didFinishWithData 가 실행이 된다.

    OAConsumer *consumer = [[OAConsumer alloc] initWithKey:kOAuthConsumerKey secret:kOAuthConsumerSecret];

    NSURL *requestTokenURL = [NSURL URLWithString:kOAuthRequestTokenURL];

    OAMutableURLRequest *request = [[OAMutableURLRequest alloc] initWithURL:requestTokenURL

                                                                   consumer:consumer

                                                                      token:nil

                                                                      realm:@"http://twitter.com/"

                                                          signatureProvider:nil];

    OADataFetcher *fetcher = [[OADataFetcher alloc] init];

    [fetcher fetchDataWithRequest:request

                         delegate:self

                didFinishSelector:@selector(requestTokenTicket:didFinishWithData:)

                  didFailSelector:@selector(requestTokenTicket:didFailWithError:)];



토큰을 받으면 이제 그 토큰과 함께 사용자를 authorize url로 이동을 시킨다.  아이폰에서는 UIWebView를 이용하여 사용자가 트위터 사이트로 이동하여 로그인 과정을 거친후 생성한 앱을 사용할지를 결정할 수 있게 한다. 대부분의 앱이 그러하듯이 나 또한 모달칭을 띄워서 사용자기 앱 내에서 트위터 로그인 과정을 거칠수 있게 한다. 위의 과정중 4번에 해당한다.
여기서 중요한점이 UIWebView의 delegate를 지정하고 URL감지를 한다는 점이다.

- (void)requestTokenTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data

{

    if (ticket.didSucceed) {

        NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        requestToken = [[OAToken alloc] initWithHTTPResponseBody:responseBody];

        NSString *authorizeURL = [[NSString alloc] initWithFormat:@"%@?oauth_token=%@", kOAuthAuthorizeURL, [requestToken.key URLEncodedString]];    

        UIViewController *vc = [[UIViewController alloc] init];

        UIWebView *webView = [[UIWebView alloc] initWithFrame:vc.view.bounds];

        [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:authorizeURL]]];

        [vc.view addSubview:webView];

        webView.delegate = self;

        UINavigationController *modalNC = [[UINavigationController alloc] initWithRootViewController:vc];

        [self presentModalViewController:modalNC animated:YES];

    }

}



아래는 UIWebView의 delegate 메소드 내용이다. UIWebView에서는 4, 5번의 과정이 진행중이다. 사용자가 최종적으로 앱을 허아 하여 콜백URL로 리다이렉트 되는것을 감지하는 코드다. 실제로 콜백 URL로 이동하지 않게 했다. 아래의 코드중 URL중 query string을 NSDictionary로 변화하는 코드는 http://stackoverflow.com/questions/3997976/parse-nsurl-query-property 에서 참고를 했다. 이동하려는 URL이 콜백URL과 같으면 URL내에 있는 oauth_verifier를 확인하고 access_token을 호출한다. 위의 과정중 7버에 해당 된다.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

{

    NSString *x = [request.URL URLStringWithoutQuery];

    if ([x isEqualToString:kOAuthCallbackURL]) {

        NSDictionary *queryDict = [[request.URL query] dictionaryFromQueryComponents];

        OAConsumer *consumer = [[OAConsumer alloc] initWithKey:kOAuthConsumerKey

                                                        secret:kOAuthConsumerSecret];

        NSURL *url = [[NSURL alloc] initWithString:kOAuthAccessTokenURL];

        OAMutableURLRequest *request = [[OAMutableURLRequest alloc] initWithURL:url

                                                                       consumer:consumer

                                                                          token:requestToken

                                                                          realm:@"http://twitter.com/"

                                                              signatureProvider:nil];

        [request setOAuthParameterName:@"oauth_verifier" withValue:[[queryDict valueForKey:@"oauth_verifier"] objectAtIndex:0]];

        OADataFetcher *fetcher = [[OADataFetcher alloc] init];

        [fetcher fetchDataWithRequest:request

                             delegate:self

                    didFinishSelector:@selector(accessTokenTicket:didFinishWithData:)

                      didFailSelector:@selector(accessTokenTicket:didFailWithError:)];

        return NO;

    }

    return YES;

}



access_token 호출이  성공이 되면 다음의 코드에서 전달 받은 값을 이용하여 token, token_secret을 저장하여 다음번 연동부터는 해당 값을 활용하여 REST API등을 호출하여 활용을 하면 된다. 여기서는 token_secret는 키체인에 저장을 하기위해 http://gorgando.com/blog/tag/sfhfkeychainutils를 참고 했다. 위의 과정중 8, 9번에 해당되는 과정이다.

- (void)accessTokenTicket:(OAServiceTicket *)ticket didFinishWithData:(NSData *)data {

    if (ticket.didSucceed) {

        NSString *responseBody = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        OAToken *token = [[OAToken alloc] initWithHTTPResponseBody:responseBody];

        NSError *error = nil;

        [SFHFKeychainUtils storeUsername:token.key andPassword:token.secret forServiceName:kOAuthConsumerName updateExisting:YES error:&error];

        if (error) {

            NSLog(@"Token save error (1)");

        } else {

            NSUserDefaults *userDetauls = [NSUserDefaults standardUserDefaults];

            [userDetauls setObject:token.key forKey:kTokenSaveKey];

            [userDetauls setBool:YES forKey:kHaveTokenKey];

            [userDetauls synchronize];

        }

        [self dismissModalViewControllerAnimated:YES];

    }

}



여기까지 해서 정리 끝.

 
1 2 3 4 5 ... 24