SwiftでTwitter OAuthを取得する oauth_token編
Swiftやらプログラミングのモチベが結構高まってきてます。
以前から苦戦していたTwitter OAuth認証をSwiftでやりたいと思ってずっと試行錯誤してました。
今日やっとoatuh_tokenを取得できたので備忘録として残します。
Twitterアプリケーションの作成
https://dev.twitter.com/
ここのTOOLS -> Manage Your Appsへリンク辿ってクライアントを作成します。
必要なものはTwitterアカウントで、今から作成する場合は電話番号を登録しないと作成できないみたいです。
あとはCreateNewAppのボタンから必要な情報を入力して
クライアントを作成します。
callbackのアドレスを入力する欄があるのですが、
この欄に何かしら値を入れておかないと認証できないとか。
ですので今回はとりあえず自分のtwitterページを登録しておく・・・
カスタムURLスキーマを設定しておけばそこを無視してくれるので特に問題ないはず。
OAuth認証
OAuth認証がいまいち理解できなかったのでメモ。
ちゃんと後から勉強して正しい情報載せたいと思うけど、
とりまTwitterの認証フローを参考にまとめてみる。
クライアントを登録するとConsumer KeyとConsumer Secretの2つが発行される。
これを使ってoauth_tokenを取得し、access_tokenを取得してAPIにアクセスする。
request発行 -> oauth_token取得 -> access_token取得 -> apiアクセス
こんな流れ。
詳しいことはちゃんともろもろ取得してからその際に・・・
oauth_token取得
access_tokenを取得するためのoauth_token, oauth_token_secretを取得するプログラムを書いた。
画面側はとりあえずButtonと書かれたボタンをクリックするとトークンを取得するようにしてる。
最初のテンプレートはSingle View Application。
ViewController.swift
import UIKit import Foundation class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @IBAction func clickButton(sender: AnyObject) { doTwitter() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // Twitter OAuth認証 func doTwitter(){ let oauth = OAuthSwift( consumerKey: Twitter["consumerKey"]!, consumerSecret: Twitter["consumerSecret"]!, requestTokenUrl: "https://api.twitter.com/oauth/request_token", authorizeUrl: "https://api.twitter.com/oauth/authorize", accessTokenUrl: "https://api.twitter.com/oauth/access_token" ) // 通信開始 oauth.start() } }
OAuthSwift.swfit
import Foundation class OAuthSwift{ // oauthリクエスト参考 // http://developer.yahoo.co.jp/other/oauth/signinrequest.html // http://www.pressmantech.com/tech/programming/1137 var dataEncoding: NSStringEncoding = NSUTF8StringEncoding var data : NSMutableData? = nil var consumer_key: String var consumer_secret: String var request_token_url: String var authorize_url: String var access_token_url: String // コンストラクタ init(consumerKey: String, consumerSecret: String, requestTokenUrl: String, authorizeUrl: String, accessTokenUrl: String){ self.consumer_key = consumerKey self.consumer_secret = consumerSecret self.request_token_url = requestTokenUrl self.authorize_url = authorizeUrl self.access_token_url = accessTokenUrl } func start() -> Void{ // クライアントアプリケーションの作成 // 流れとして request_token投げる -> ouath_token取得(今ここ) -> authrize_request投げる -> access_token取得 -> API使用 // リクエストURL設定 let twitterURL : NSURL = NSURL(string: "https://api.twitter.com/oauth/request_token")! // request var request : NSMutableURLRequest = NSMutableURLRequest(URL: twitterURL); request.HTTPMethod = "POST" request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData // oauth_request設定 var param = Dictionary<String, String>() // バージョン param["oauth_version"] = "1.0" // 証明書アルゴリズム(Twitterでは固定) param["oauth_signature_method"] = "HMAC-SHA1" // ConsumerKey param["oauth_consumer_key"] = self.consumer_key // Unixタイムスタンプ param["oauth_timestamp"] = String(Int64(NSDate().timeIntervalSince1970)) // ランダムな文字列 param["oauth_nonce"] = (NSUUID().UUIDString as NSString).substringToIndex(8) // コールバック param["oauth_callback"] = "oauth-swift://" // 証明書 param["oauth_signature"] = self.oauthSignatureForMethod("POST", url: twitterURL, 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])\"") } } // 最終的にリクエストするのは下記文字列 // "OAuth oauth_callback=\"swift-oauth%3A%2F%2Fswift-oauth%2F\", oauth_consumer_key=\"CXubzXLR2vzqbCf1d9maSJ4ob\", oauth_nonce=\"46B623F4\", oauth_signature=\"%2Brx8F1ofGHhQe0iN%2Ff7MArz05F4%3D\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"1422107013\", oauth_version=\"1.0\"" // リクエスト設定 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)!) } } // サーバからレスポンスを受け取ったときのデリゲート func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) { // Recieved a new request, clear out the data object self.data! = NSMutableData() } // サーバからデータが送られてきたときのデリゲート func connection(connection: NSURLConnection!, didReceiveData data: NSData!){ self.data!.appendData(data) } // データロードが完了したときのデリゲート func connectionDidFinishLoading(connection: NSURLConnection!){ // バイナリデータが発行される let html : String = NSString(data: self.data!, encoding: NSUTF8StringEncoding)! // コンソールに出力 println(html) } // signature作成 func oauthSignatureForMethod(method: String, url: NSURL, parameters: Dictionary<String, String>) -> String { let signingKey : String = "\(self.consumer_secret)&" let 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) } // Dictionary内のデータをエンコード func urlEncodedQueryStringWithEncoding(params:Dictionary<String, String>) -> String { var parts = [String]() for (key, value) in params { let keyString = urlEncodedStringWithEncoding(key) let valueString = urlEncodedStringWithEncoding(value) let query = "\(keyString)=\(valueString)" as String parts.append(query) } return "&".join(parts) as String } // URLエンコード func urlEncodedStringWithEncoding(str: String) -> String { let charactersToBeEscaped = ":/?&=;+!@#$()',*" as CFStringRef let charactersToLeaveUnescaped = "[]." as CFStringRef var raw: NSString = str let result = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, raw, charactersToLeaveUnescaped, charactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(dataEncoding)) as NSString return result } // SHA1署名のハッシュ値を作成 func SHA1DigestWithKey(base: String, key: String) -> NSData { let str = base.cStringUsingEncoding(dataEncoding) let strLen = UInt(base.lengthOfBytesUsingEncoding(dataEncoding)) let digestLen = Int(CC_SHA1_DIGEST_LENGTH) let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen) let keyStr = key.cStringUsingEncoding(NSUTF8StringEncoding) let keyLen = UInt(key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)) CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), keyStr!, keyLen, str!, strLen, result) return NSData(bytes: result, length: digestLen) } }
はい、https://api.twitter.com/oauth/request_token にPOSTでリクエスト送るだけ。
ちょっと制限があって、query stringのパラメータ名が昇順じゃないといけないとかハッシュ値作成しなきゃいけないとか。
その辺はプログラムソース見てもらえれば、全部できたら改めて書き直すと思う。
これから
ライブラリ使えばこの辺一発でできるんだけどそれだと理解できないから作っただけ、自己満足。
とりあえずツイート取得、投稿、その他昨日を提供できるようにすることとOAuthTwitterを綺麗にしたりHTTPClient作ったりすることを進めていく。
あとは忘れないようにちゃんとこの辺の知見のまとめをこのブログに残しておく。
最低限これをやっていこうと思う。
近いうちにまずはツイート取得を目指していこう!