ここ最近記事書くのをサボってました.すみません.今回はSSL redirectの話.
Rails 3.1以降ではforce_sslを使うことで,サイト全体や特定のactionについて,SSLを強制することができます.
しかし,force_sslではHTTP -> HTTPSへのリダイレクトはできるのですが,その逆のHTTPS -> HTTPへのリダイレクトをサポートしていません.
つまり,force_sslを使った場合,一度httpsのURLに入ってしまった後に相対パスでリンクを遷移すると,httpアクセスで構わないページもhttpsでアクセスしてしまうことになります.
場合によってはHTTPSでアクセスする必要の無いページはできるだけHTTPでアクセスしてほしい,というケースがあるので,今回はそういったことを実現しようぜ,という話です.
このような処理が必要とされるケースとしては以下のものが考えられます.
- アクセスの多いサイトにおいて,サーバ側でのSSL/TLSの暗号化処理が処理上のボトルネックになる場合
- サイト上の一部のページにおいて外部HTTPサイトに置かれた画像やJavaScriptを読み込む必要がある
1番目はSSLアクセラレータやAWS ELB等を使っていれば関係ないですが,Webサーバ側でSSLサーバを兼任している場合に問題になることがあります.SSL/TLSの処理はアクセスが多いと意外と馬鹿にならないのでサイトの規模によっては必要になるでしょう.
2番目は外部APIやWidgetを使ったりするケースです.サービスによっては証明書付きのHTTPSサーバを用意していないサービスや,HTTPSのURLは有償サービスになっているものもあるため,そういったサービスを使っている場合には警告が表示されてしまいます.
そんなわけで,上記に当てはまるような問題を抱えている人はどうぞ.
Rails付属のforce_ssl
簡単にまとめると,force_sslができることは以下の二点です.
- force_ssl指定したアクションにHTTPアクセスされた場合,HTTPSにリダイレクトさせる
- force_ssl指定されていないアクションについては,HTTP/HTTPSどちらでもアクセスできる
図とサンプルコードにすると以下の様になります.
上記の例で言うと,hoge#baaなどにHTTPアクセスしているときにはHTTPのURLを行き来しているのですが,一度hoge#fooにアクセスしてHTTPSにリダイレクトしてからhoge#baaにアクセスすると,HTTPSである必要は無いのにHTTPSで通信が行われます.
ViewのリンクURLが「XXX_path」で書かれている場合は相対パスになるので,一度HTTPSに入ってしまうとHTTPに戻って来れないという状態になってしまうわけです.
bartt/ssl_requirement
こうした問題を回避するにはbartt/ssl_requirementを使うことで,httpsアクセスさせたくないURLにアクセスされたときにはhttpリダイレクトするといった処理を実現することができます.
ssl_requirementはもともとRailsの公式ツリーの中でdhhが作成したものだったのですが,この機能がRails 3.1で取り込まれる際にforce_sslというDSLを使う形式になりました.
そのため,Railsツリーにあるssl_requirementが対応しているRailsバージョンは古く,最近のRails 3.2.13等で使うためにはfork版のbartt/ssl_requirementを使う必要があります.
基本的な使い方はGithubのREADME通りなので省略します.
bartt/ssl_requirementを使うには,DSLとしてssl_requiredとssl_allowedをcontrollerの先頭に記述することで,sample#required_actionにHTTPでアクセスすると自動的にhttps付きのURLにリダイレクトされます.
また,,sample#allowed_actionへはHTTP/HTTPSどちらでもアクセスすることができます.
一点注意としては,ssl_requirementをincludeすると,特にssl_requrired/ssl_allowedの指定が無いactionはHTTPアクセスが強要されます(HTTPSでアクセスするとHTTPにリダイレクトされる).
図にすると以下の様になります.
bartt/ssl_requirementの問題点
通常のサイトであれば,bartt/ssl_requirementを使えばやりたいことは実現できるのですが,さらにもうちょっと気の利いたことをやろうとすると,困ってしまいます.
bartt/ssl_requirementはactionに対してSSL設定の有無を決めるので,そのままではactionの内容や特定の条件でSSL設定を切り替えることができません.
具体的に直面したケースとしては,PC/スマートフォン両対応サイトで,同じactionを共有しているケースで,PCの場合にはHTTP強制,スマートフォンの場合はHTTPSでアクセスさせたいといったことがありました.
PCでHTTP強制したかった理由は前述の通り外部APIの制限があったためです.
bartt/ssl_requirementをhackして融通を利かせる
bartt/ssl_requirementのデフォルトではやりたいことができなかったので元のソースを眺めてみました.
すると,SslRequirementモジュールは読み込み時にensure_proper_protocolをbefore_filterとしてセットし,SSL必須かどうかのチェックはssl_required?メソッドによって判別していることが分かりました.
使っているメソッドまで分かれば後はoverrideするだけで良いので,以下の様に実装してみました.
これで,想定通りやりたかったことが実現できました.