はじめに
TwitterなどとOAuth連携するWebサイトの開発でハマっているがあったので紹介します。
OAuthとは
あるサービスにTwitterやFacebookのアカウントを用いてログインするときに、以下の様な確認画面が出てきて認証を行います。
OAuthはこれを支える裏側の認証フローの仕組みで、ユーザフレンドリなアカウント情報のやり取りを実現します。
そもそもOAuthってどうして必要なのかという点についてはこの辺りを参考に。
処理シーケンス
OAuthは複雑な処理フローに見てますが実際のブラウザの動きから抑えれば難しくないです。
トラブルシューティングする際はどこで問題が発生したかが重要な切り分け材料となるので、概念レベルで全体像を把握しておきます。
【参考】SECURE YOUR REST API WITH OAUTH2 IMPLICIT GRANT
http://www.ibuildings.com/blog/2013/03/secure-your-rest-api-oauth2-implicit-grant
また、上記がとっつきにくい場合はこちらの記事の方が理解しやすくお勧めです。
OAuth 2.0でWebサービスの利用方法はどう変わるか
http://www.atmarkit.co.jp/fsmart/articles/oauth2/01.html
ハマりどころ
リダイレクトすると404や503エラー
Twitterの開発者メニューで登録するリダイレクトIPですが、開発環境のIPと本番環境で同じIPでは動きません。
例えば、開発環境では127.0.0.1
で上手くいっても、本番環境では上手く動きません。
この127.0.0.1
は自分自身を示すIPなので、本番サーバからリダイレクトされたIPが127.0.0.1
だった場合、自分自身のローカルPCにアクセスしにいくため正しく動作しません。
開発が終了したら本番環境のサーバIPを登録しましょう。
その際、一度本番環境サーバのIPを設定すると開発機では動かなくなるので注意しましょう。
セッションが消える!
ホスト名一致しない編
リダイレクトのIP(ホスト名)と同一のIP(ホスト名)でログイン処理〜認証情報を取得処理を行う必要があります。
特に開発時はlocalhost
で動作確認している方が多いと思いますが、OAuth認証
の疎通テストではログイン画面とリダイレクト先のホスト名が異なってしまうと、同一セッションとみなされなくなり上手く動かないため注意です。
理由は同一セッションかどうかを判定するセッションIDはCookie
に保持され、そのCookie
の値はホスト名の文字列に紐付いて管理されるためです。
DNSで解決したIP単位で保持されず、単純にURLバーの値に紐付いて保持されるので注意です。
以下のStackoverflowにも似たような質問が飛んでました。
IP and domain create different session
http://stackoverflow.com/questions/11836914/ip-and-domain-create-different-session
対応としては開発マシンのIPを登録した場合は、ログイン画面からそのIPで疎通確認を行います。
例えば、開発時にリダイレクト先URLのIPを127.0.0.1
にした場合はlocalhost
でログイン画面にアクセスするのではなく127.0.0.1
でアクセスします。
プロトコルの壁編
①HTTPS(自分のアプリ)→②HTTPS(認証処理)→③HTTP(自分のアプリ)だと、1⇔3間でセッションIDが変わってしまいます。
(セッションIDはCookieに保存されていますが、Cookieの仕様で異なるプロトコルをまたがっての利用を許容しないため値が変わります)
そのため1のタイミングでセッションに認証情報を保持させても、3のタイミングでは別セッション扱いになるので1で載せた情報を取得できず、あたかもセッションが霧散したように見えます。
この理由に併せて、OAuthのサンプル実装では認証用のオブジェクトをセッションスコープで保持して連携する方式になっていることが多いので、サンプルコード通りに実装したのに連携が失敗するという現象が発生します。
Twitter4jを用いたServletのOAuthのサンプルコードは以下のサイトを参考に。
GAE/JとTwitter4JでTwitterのOAuth
https://zenjiro.wordpress.com/2010/06/19/gaej%E3%81%A8twitter4j%E3%81%A7twitter%E3%81%AEoauth/
HTTP⇔HTTPS間のセッションの話は以下を参考に。
There is a reason that cookies are not allow to cross protocol boundaries - it is an attack vector!
http://stackoverflow.com/questions/4635425/tomcat-keep-session-when-moving-from-https-to-http
解決方法
最も確実でシンプルな対応方法は、全ての通信をHTTPSで動くようにすることです。
HTTPS通信の設定方法は後述します。
ちなみにHTTPS通信の開始タイミングですが、ユーザ名/パスワードをPOSTするタイミングではなく入力フォームをGETさせる段階でHTTPSにすべきです。(つまりログイン画面からログアウト画面まで全てHTTPSで通信させる)
理由は下記に記載されています。
ログイン画面がhttpsではない理由なんて無い!
http://might1976.doorblog.jp/archives/51035103.html
よく見かけたNG対応集
セキュリティ的にも実装的にも問題がある手法ですが、たまに暫定対応で突っ走ったコードを見ることが多かったので共有します。
全て自アプリケーションはHTTPプロトコルのみで動いている前提です。
1.アプリケーションスコープ
セッションではなくアプリケーションスコープに載せれば動いた!という声をたまに聞きますが、複数人が同時にOAuth認証でログインすると不具合を起こすのでNGです。
アプリケーションスコープを利用するとセッションスコープと違って値が消えない理由は、セッションIDがどのような値になってもアプリケーションスコープの値は常に取得可能だからです。
2.Cookieと組み合わせる
認証サーバ連携前のセッションIDをCookie
に入れて、連携後の処理でCookieからセッションIDを復元、セッションスコープやアプリケーションスコープに格納された認証情報を取得する方法も考えられますが、以下の理由で厳しいです。
2.1. Cookie&セッションスコープ連携
標準のServletAPIでは自分のセッションIDに紐付かないセッションにアクセス出来ないので実装できません。
2.2. Cookie&アプリケーションスコープ連携
アプリケーションスコープに保持するためのキーを「適当なプレフィクス+セッションID」にして紐付ける手法です。
セッションIDをキーにすることで一意性が担保出来るため表面上の動作は問題なく動きます。
が、HTTP通信が残るため万が一にも連携用のセッションIDが盗聴される可能性があるのでセキュリティホールになります。
TomcatのHTTPS設定
TomcatでHTTPS通信を行わせる手法を説明します。
作業フロー
- JDKの付属ツール
keytool
でSSL証明書を作成 - Tomcatの定義ファイルである
server.xml
を編集 - 動作確認その1
- IDE(Eclipse)への環境の反映
- 動作確認その2
以下の記事の.keystore
ファイルの作成+server.xml
編集を参考にしました。
サーブレット 11章 認証方式 ―TECHSCORE
http://www.techscore.com/tech/Java/JavaEE/Servlet/11-3/
環境確認
環境変数には以下が設定されていることとします。
JAVA_HOME
とJRE_HOME
とCATALINA_HOME
の3つの変数です。
JRE_HOME
はTomcatの起動時に必要です。
Windowsで環境変数を設定するときは、Windowsキー
+ Pause
でシステム画面を開いて、システム詳細設定>環境変数(N)を選択し、各変数を設定します。
IDE(Eclipse)経由でしかTomcatを起動しない場合は、JAVA_HOMEの設定のみで問題無いです。
(その場合は.keystoreファイル作成後、Eclipse設定までスキップして下さい)
.keystoreファイル作成
姓名、組織単位名、組織名、などはなんでも良いです。
[いいえ]の問にはy
を入力します。
最後の鍵パスワード入力無しでスキップ可能です。
# コマンドの実行
"%JAVA_HOME%/bin/keytool" -genkey -keyalg RSA -alias example.com -keystore .keystore -storepass tomcat
姓名は何ですか。
[Unknown]: tomcat
組織単位名は何ですか。
[Unknown]:
組織名は何ですか。
[Unknown]:
都市名または地域名は何ですか。
[tokyo]: tokyo
都道府県名または州名は何ですか。
[osaki]: tokyo
この単位に該当する2文字の国コードは何ですか。
[jp]: jp
N=tomcat, OU=Unknown, O=Unknown, L=tokyo, ST=tokyo, C=jpでよろしいですか。
[いいえ]: y
www.test.com>の鍵パスワードを入力してください
(キーストアのパスワードと同じ場合はRETURNを押してください):
このコマンドでカレントディレクトリに.keystore
ファイルが生成されます。
目検で確認するか以下のコマンドdえ確認して下さい
$ dir /b "%JAVA_HOME%\.keystore"
.keystore
配備
生成された.keystore
ファイルをTomcatのホームディレクトリに手動コピーします。
$ dir /b "%CATALINA_HOME%\.keystore"
.keystore
設定
Tomcatのserver.xml
を編集します。
confフォルダ以下にserver.xml
は存在します。
編集内容は以下です。
- 8443ポート記載部分のコメントアウトを外して有効にします
- さらに
keystoreFile
とkeystorePass
の2属性を追加
keystorePassの設定値は先ほど設定したパスワードと同じものを設定します
【消す→】 <!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile=".keystore" keystorePass="tomcat"/> 【←ここ追加】
--> 【←消す】
起動
Tomcatを起動します。
$ "%CATALINA_HOME%/bin/startup.bat"
以下のように8443のProtocolHandlerが起動するログが出ていれば上手く読み込めています。
24-Jun-2015 11:14:59.250 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-apr-8080"]
24-Jun-2015 11:14:59.260 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8443"] 【←これが出てればOK】
24-Jun-2015 11:14:59.267 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-apr-8009"]
確認
以下にURLにアクセスする。
アクセスURLのhttp
がhttps
に、ポートが8443
なのに注意すること。
https://${server_ip}:8443/
以下のようなマネージャー画面にログイン出来れば成功です。
Eclipse経由でTomcatを起動する場合の設定
EclipseのWTP(Web Tools Platform)で開発していると、前述の対応を実施してもEclipseからTomcatを起動した時に8443ポートが有効になっていません。
これに対応するには以下2つの対応が必要です。
Serversのserver.xml書き換え
これはServers
のPJにTomcatのserver.xml
がコピーされているための措置です。
Tomcatのconfフォルダ以下と同様にこちらも8443部分のコメントを外すことと、keystoreFile
とkeystorePass
の2属性を追加書き換えが必要です。
【参考】Servers Project(EclipseのWTPで自動生成されるPJ)
【参考】Servers以下のserver.xmlもTomcat/conf以下と同様に編集する
EclipseのWSTプラグインに.keystoreファイルをコピー
更にC:\XXX\pleiades\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0
以下に.keystore
をコピーします。
これはEclipse経由でTomcatを起動すると、定義ファイルのロードがEclipseのメタデータのフォルダをロードする仕様だからです。
ちなみに、OS Xだと以下のパスになります。
/Users/<username>/Documents/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/conf/keystore.key
以上でEclipse経由でTomcat Serverを立ち上げてもHTTPSによるアクセスが可能となります。
Eclipse経由でTomcatを起動して、ブラウザ経由でアクセス可能か確認して下さい。
https://${server_ip}:8443/
以下参考
Eclipse WTP: How do I enable SSL on Tomcat?
http://stackoverflow.com/questions/951890/eclipse-wtp-how-do-i-enable-ssl-on-tomcat
本番環境への設定
ローカルでの開発環境と同様に.keystore
ファイルを本番環境のサーバデプロイ + server.xml
を編集して完了です。
HTTPとHTTPSの混在サイトについて
ECの様にレスポンスタイムが命!というところはオープンに出来るページをHTTP通信にしてプロトコルを混在させているサイトもあります。
その場合は【メモ】httpとhttpsの混在環境でクッキーを利用する - Qiitaで説明されているような処理を加えているものと考えられます。
とは言っても、おそらくサーバサイド + クライアントサイドを合算して数十ms程度の節約にしかなら無いと思うので、相当神経質なサイト以外は対応の優先度は低いかなと思います。
おしまい
OAuthを使うときはHTTPS通信が基本!
そうでなくても基本的にHTTPS通信しましょ!