SwiftでTwitter OAuthを取得する access_token編
ついにTwitterAPIの認証部分をクリアしてタイムラインを取得するところまでできましたのでメモ。
ここまでできればあとは整えてクリアだ!!とか思ってたらまた壁にぶつかったので今日はここまでにしてこれまでの内容をまとめます。
oauth_tokenを使用してaccess_tokenを取得する
前回の記事ではrequest_tokenにパラメータを投げてトークンを取得するところまでやりました。
SwiftでTwitter OAuthを取得する oauth_token編 - umegusa's blog
今回はここの続きです。
request_tokenを取得して認証とaccess_tokenを取得するところまでやります。
まず、トークンを取得します。
ここは前回のコードを参照してください。
// 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) // Authorize self.authorizeClient(NSString(data: data, encoding: self.dataEncoding)!) }
トークンを取得したらAuthorizeClientメソッドに飛びます。
Authorizeは下記のように実装します。
// Authorize func authorizeClient(param: String) { var divideParam = param.componentsSeparatedByString("&") var oauthToken = divideParam[0].componentsSeparatedByString("=")[1] let queryURL = NSURL(string: self.authorize_url + "?oauth_token=\(oauthToken)") // safariで認証 UIApplication.sharedApplication().openURL(queryURL!) }
本当はWebViewを使って認証したかったのですが、今回はsafariを使用してそのまま認証させます。
Authorizeはrequest_tokenで取得したパラメータをgetメソッドで投げるだけです。
そうすると認証画面に飛ぶことができます。
ただし、ここでの注意点は下記2点です。
- アプリケーション作成時に何でもいいからコールバックURLを入力する
- これを入れないとoauth_callbackパラメータが無効になってしまう
- oauth_callbackパラメータはプロトコル部分だけだとうまくいかない
うまくいくとsafariが立ち上がって下記認証画面へ進み、アカウントで認証が成功したらアプリに戻ってきます。
よく見る画面ですね。
コールバックされるとoauth_token, oauth_verifierのパラメータが付与されたURLが返ってきます。
oauth-swift://hogehoge/?oauth_token=sakldsjalkdjdkljfasldf&oauth_verifier=klajlfhjagdkasgf
※ 上記パラメータはダミーです
カスタムURLでアクセスされた情報を取得する
AppDelegate.swiftに下記のようにapplicationメソッドを用意することで取得することが可能です
AppDelegate.swift
func application(application: UIApplication!, openURL url: NSURL!, sourceApplication: String!, annotation: AnyObject!) -> Bool { ViewController.handleOpenURL(url) return true }
今回はViewControllerにhandleOpenURLという静的メソッドを実装してそちらに飛ぶように実装しています。
(内容がひどいのでNSNotificationCenterをうまく使って実装したい。。。)
尚、静的メソッドは下記のように実装できます。
class func handleOpenURL(url: NSURL) { println("OK!!") }
class func メソッド名 で静的メソッドの実装ができます。
ここでoauth_token, oauth_verifierを使用してaccess_tokenを取得しにいきます。
access_tokenを取得する
ここまで来たらもう少しです!
access_tokenの取得方法は基本的にリクエストトークンの取得と同じです。
ただし、証明書の発行や送信に使用するパラメータに差異があります。
// get access token func postAccessTokenWithRequestToken(url: NSURL){ // 基本的にリクエストトークンと同じだが、リクエストに差異がある // ここで設定するのは下記パラメータ // - oauth_token // - oauth_verifier var param = Dictionary<String, String>() var query : String = url.query! var divideParam = query.componentsSeparatedByString("&") for paramStr in divideParam { var splitParam = paramStr.componentsSeparatedByString("=") param[splitParam[0]] = splitParam[1] } // oauth_param param["oauth_version"] = "1.0" param["oauth_signature_method"] = "HMAC-SHA1" param["oauth_consumer_key"] = self.consumer_key param["oauth_timestamp"] = String(Int64(NSDate().timeIntervalSince1970)) param["oauth_nonce"] = (NSUUID().UUIDString as NSString).substringToIndex(8) param["oauth_signature"] = self.oauthSignatureForMethod("POST", url: NSURL(string:self.access_token_url)!, parameters: param) // アルファベット順に並べ替える var authorizationParameterComponents = urlEncodedQueryStringWithEncoding(param).componentsSeparatedByString("&") as [String] authorizationParameterComponents.sort { $0 < $1 } // リクエスト文字列作成 var headerComponents = [String]() for component in authorizationParameterComponents { let subcomponent = component.componentsSeparatedByString("=") as [String] if subcomponent.count == 2 { headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"") } } // request var request : NSMutableURLRequest = NSMutableURLRequest(URL: NSURL(string: self.access_token_url)!); request.HTTPMethod = "POST" request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData // カスタムヘッダ設定 request.setValue("OAuth " + ", ".join(headerComponents), forHTTPHeaderField: "Authorization") // 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) } }
上記により、access_token(パラメータ上はoauth_token, oauth_token_secret)を取得できます。
リクエストトークンの取得と差異があったのは先ほど取得したパラメータを使用するというところのみになります。
ここで取得したoauth_token, oauth_token_secretを使用して、TwitterAPIにアクセスします。
home_timelineを取得する
Twitter OAuth v1.1になって毎回カスタムヘッダを作成しなければ情報を取得できなくなってしまったみたいですね。
ということでタイムラインを取得するAPIを使用して取得してみましょう。
// タイムライン取得 func getUserTimeline(tokenParam: String) -> Void{ var param = Dictionary<String, String>() var divideParam = tokenParam.componentsSeparatedByString("&") for paramStr in divideParam { var splitParam = paramStr.componentsSeparatedByString("=") if(splitParam[0] == "oauth_token"){ self.access_token = splitParam[1] }else if(splitParam[0] == "oauth_token_secret"){ self.access_token_secret = splitParam[1] } } // 取得に必要なパラメータ // oauth_tokenが新規に追加される param["oauth_token"] = self.access_token param["oauth_version"] = "1.0" param["oauth_signature_method"] = "HMAC-SHA1" param["oauth_consumer_key"] = self.consumer_key param["oauth_timestamp"] = String(Int64(NSDate().timeIntervalSince1970)) param["oauth_nonce"] = (NSUUID().UUIDString as NSString).substringToIndex(8) param["oauth_signature"] = self.oauthSignatureForMethod("GET", url: NSURL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!, parameters: param) // http header用パラメータ作成 // アルファベット順に並べ替える var authorizationParameterComponents = urlEncodedQueryStringWithEncoding(param).componentsSeparatedByString("&") as [String] authorizationParameterComponents.sort { $0 < $1 } // リクエスト文字列作成 var headerComponents = [String]() for component in authorizationParameterComponents { let subcomponent = component.componentsSeparatedByString("=") as [String] if subcomponent.count == 2 { headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"") } } // request var request : NSMutableURLRequest = NSMutableURLRequest(URL: NSURL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json")!); request.HTTPMethod = "GET" request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData // リクエスト設定 request.setValue("OAuth " + ", ".join(headerComponents), forHTTPHeaderField: "Authorization") // 非同期通信開始 NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){ response, data, error in if(error != nil){ // エラー文言表示 println(error.description) } // oauth_token表示 print(NSString(data: data, encoding: self.dataEncoding)!) } }
HMAC-SHA1でパラメータとキーを渡してSHA1ハッシュ値にしてbase64エンコードするところは同じなのですが、ユーザ情報を取得する場合、下記のような変更があります。
// signature作成 func oauthSignatureForMethod(method: String, url: NSURL, parameters: Dictionary<String, String>) -> String { // URLとパラメータをHMAC-SHA1ハッシュ値に変換してbase64エンコードする var signingKey : String = "\(self.consumer_secret)&" var signingKeyData = signingKey.dataUsingEncoding(dataEncoding) // access tokenを取得したらkeyにaccess token secretを付加する必要がある signingKey = "\(self.consumer_secret)&\(self.access_token_secret)" signingKeyData = signingKey.dataUsingEncoding(dataEncoding) // パラメータ取得してソート var parameterComponents = urlEncodedQueryStringWithEncoding(parameters).componentsSeparatedByString("&") as [String] parameterComponents.sort { $0 < $1 } // query string作成 let parameterString = "&".join(parameterComponents) // urlエンコード let encodedParameterString = urlEncodedStringWithEncoding(parameterString) let encodedURL = urlEncodedStringWithEncoding(url.absoluteString!) // signature用ベース文字列作成 let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)" let signatureBaseStringData = signatureBaseString.dataUsingEncoding(dataEncoding) // signature作成 return SHA1DigestWithKey(signatureBaseString, key: signingKey).base64EncodedStringWithOptions(nil) }
consumer_key_secretだけでなく取得したoaut_token_secretも連結してキーに渡して証明書を作成する必要があります。
ここで引っかかって全然取得できなかったのは良い思い出です。。。
成功すると下記のようなjsonが返ってきます。
[ { "contributors": null, "coordinates": null, "created_at": "Sun Feb 15 09:09:26 +0000 2015", "entities": { "hashtags": [], "media": [ { "display_url": "pic.twitter.com/Jsu0oYxcGK", "expanded_url": "http://twitter.com/NIKEiD_JP/status/566884628070682624/photo/1", "id": 566884626841747456, "id_str": "566884626841747456", "indices": [ 139, 140 ], ...... } ], // 長いので省略 "favourites_count": 0, "follow_request_sent": false, "followers_count": 169270, "following": true, "friends_count": 15, "geo_enabled": false, "id": 1946049950, "id_str": "1946049950", "is_translation_enabled": false, "is_translator": false, "lang": "ja", "listed_count": 483, "location": "NIKE", "name": "NikeStoreJapan", "notifications": false, "profile_background_color": "131516", "profile_background_image_url": "http://pbs.twimg.com/profile_background_images/378800000106403467/c19a3d26691f9eb4bb431ad3f87676ad.jpeg", "profile_background_image_url_https": "https://pbs.twimg.com/profile_background_images/378800000106403467/c19a3d26691f9eb4bb431ad3f87676ad.jpeg", "profile_background_tile": true, "profile_banner_url": "https://pbs.twimg.com/profile_banners/1946049950/1418890257", "profile_image_url": "http://pbs.twimg.com/profile_images/472164680534732800/brtYH1Lj_normal.jpeg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/472164680534732800/brtYH1Lj_normal.jpeg", "profile_link_color": "009999", "profile_location": null, "profile_sidebar_border_color": "FFFFFF", "profile_sidebar_fill_color": "EFEFEF", "profile_text_color": "333333", "profile_use_background_image": false, "protected": false, "screen_name": "NikeStoreJapan", "statuses_count": 1393, "time_zone": "Tokyo", "url": "http://t.co/nKcfAyJlq0", "utc_offset": 32400, "verified": true } } ]
成功しましたね!
あとはjsonをパースして表示するだけです。
終わりに
一応home_timelineは取得できましたが、また次の壁に出会いました。
カスタムヘッダではなくAPIのパラメータを付加して何かする(つぶやくなど)ことがまだ実現できていません。
そのほか色々やることがまだまだある予感です。
- safariじゃなくて WebViewで認証画面出してもろもろ操作したい
- つぶやくなどパラメータを付加してデータ取得したい
- そもそもタイムライン並べてない(jsonパーサ作ろう)
- ソースコードももっと汎用性を持たせてライブラリ化しておきたい
などなど・・・
先は長そうですね・・・・
でも認証周りが成功したということはほぼ終わったも同然だと考えていますので、
さくさく進められたらいいなと思う今日この頃です!