Twitter でOAuth 認証してTweetする on Tornado
6月30日にいきなりtwitterアプリが使えなくなる!?twitterのベーシック認証廃止について | ついーたーTweeter.jpという記事をみて、そういえばTwitter関連のアプリの作ったことないことに気づいて、調べて見ました。
ちょっと作ってみたいものもありまして、ついでなのでTornadoで試してみました。
でOAuthって?
あるサービスの認証情報を利用して、別のサービスの管理する情報を扱えるようにする仕組みのようです。
すみません。まだ私も理解し切れていません。下の説明を読むことをおすすめします。
↓一通りの知識はここから
ゼロから学ぶOAuth:特集|gihyo.jp … 技術評論社
↓図がすごくわかりやすい
OAuthプロトコルの中身をざっくり解説してみるよ - ゆろよろ日記
前準備
OAuthするにはConsumer KeyとConsumer Secretを取得する必要があります。
↓の記事が詳しいので参考にしてください。
Sinatra と OAuth を使って Twitter のタイムラインを取得してみた - まちゅダイアリー(2009-08-18)
Tornadoの場合、ちょっとだけ違うのは以下の通りです。
- "Application Type"は"Browser"を使う
- "Callback URL"は必ず指定し、Login処理をするURLを指定する。(例 http://www.example.jp/auth/login)*1
やり方
Tornadoの場合はauth用のモジュールがありますので、これを使います。↓を参照
サードパーティ認証
コード例
import tornado.auth import tornado.escape import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web from tornado.options import define, options define("port", default=8888, help="run on the given port", type=int) define('twitter_consumer_key', default="Twitterで取得するConsumerKey", help="For Twitter Oauth") define('twitter_consumer_secret', default="Twitterで取得するConsumerSecret", help="For Twitter Oauth") class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", MainHandler), (r"/message/home",HomeTimelineHandler), (r"/message/update",UpdateHandler), (r"/auth/login", AuthHandler), (r"/auth/logout", AuthLogoutHandler), ] settings = dict( twitter_consumer_key = options.twitter_consumer_key, twitter_consumer_secret = options.twitter_consumer_secret, cookie_secret="32oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=", login_url="/auth/login", xsrf_cookies=True, ) tornado.web.Application.__init__(self, handlers, **settings) class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): user_json = self.get_secure_cookie("user") if not user_json: return None return tornado.escape.json_decode(user_json) def save_current_user(self, user): self.set_secure_cookie("user", tornado.escape.json_encode(user)) class MainHandler(BaseHandler, tornado.auth.TwitterMixin): @tornado.web.authenticated def get(self): try: name = tornado.escape.xhtml_escape(self.current_user["username"]) try: aaa = self.current_user["access_token"] except: self.write("access_token don't exist") self.write("Hello, " + name) except: self.write('Please <a href="/auth/login">LogIn</a>') class HomeTimelineHandler(BaseHandler,tornado.auth.TwitterMixin): @tornado.web.authenticated @tornado.web.asynchronous def get(self): self.twitter_request( "/statuses/home_timeline", access_token=self.current_user["access_token"], callback=self.async_callback(self._on_get)) def _on_get(self,tweets): if not tweets: tweets = [] for tweet in tweets: self.write(tweet['user']["screen_name"] + ":" + tweet["text"] + "<br />") self.finish() class UpdateHandler(BaseHandler, tornado.auth.TwitterMixin): @tornado.web.authenticated @tornado.web.asynchronous def get(self): self.twitter_request( "/statuses/update", post_args={"status": "Testing Tornado Web Server"}, access_token=self.current_user["access_token"], callback=self.async_callback(self._on_post)) def _on_post(self, new_entry): if not new_entry: # Call failed; perhaps missing permission? self.authorize_redirect() return self.finish("Posted a message!") class AuthHandler(BaseHandler, tornado.auth.TwitterMixin): @tornado.web.asynchronous def get(self): if self.get_argument("oauth_token", None): self.get_authenticated_user(self.async_callback(self._on_auth)) return self.authenticate_redirect() def _on_auth(self, user): if not user: raise tornado.web.HTTPError(500, "Twitter auth failed") # Save the user using, e.g., set_secure_cookie() self.set_secure_cookie("user", tornado.escape.json_encode(user)) self.redirect(self.get_argument("next", "/")) class AuthLogoutHandler(BaseHandler, tornado.auth.TwitterMixin): def get(self): self.clear_cookie("user") self.redirect(self.get_argument("next", "/")) def main(): tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(Application()) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": main()
まだよくわかってないこと
OAuthのバージョンについて
TornadoのOAuth認証はOAuthのver1.0にのみ対応してます。
でも近頃のTwitter OAuth関連のBlogを見てるとver1.0a対応してる記事が多いです。
例えば↓
Tornadoでも1.0a対応してる人たちがいて、Notes on making OAuth work with Google - Tornado Web Server | Google Groupsなんて言う話題がMLに流れてます。
Joe Bohmanさんが対応していて現状↓のように対応済みの様子ですね。(ただしリポジトリ版、しかも本家にはまだ取り込まれていません)
404 · GitHub
YoutubeのOAuth対応もされているようで、早く本家に取り込まれることを願ってやみません。
結局サービスによって対応バージョンがまちまちなのかなぁ
ところで
Tornadoのauthでやってるのは↓によると3-legged認証っていうらしい。
tweepyでtwitterの3-legged OAuth認証を試してみた(GoogleAppEngine) « taichino.com
となると、Twittet Botとか作るにはちょっとオーバースペックな認証方法なのかも。
Tornadoでのさらなるコード例
↓のところで、さらに高機能なTornado製のWeb版Twitter Clientが公開されてます。Tornadoらしい書き方がされていてかなり勉強になります。
http://github.com/foremire/TwitTornado/blob/local/twittornado.py
また、Web版Twitter ClientAIM | Chat, Share, ConnectもTornadoとのこと。Brizzlyの制作チームはTornadoにパッチを送ったりしてかなりFW開発に協力しているようです。
まとめ
TornadoはOAuth認証機能を自前で持っていて追加パッケージなしに割合簡単に使うことができます。
この機能は実はgaemaでそのまま利用されています。これを利用するとGoogle App EngineでOAuthが使えるようになります。
kay-framework - A web framework made specifically for Google App Engine - Google Project Hostingでも取り込まれているので是非使ってみるとApp EngineでOAuthを使ったいろいろなサービスが作れるようになると思います。
*1:私はここではまって1日無駄にしましたorz