【解決法】The change you wanted was rejected. [Rails]
AWS上にRails(Nginx+Unicorn)でサイトを作っていたら、SSL化後にこんなエラーが発生した。
サイト内で新規投稿をしたときに発生した。他のページはちゃんと表示される。どうやらPOSTの時のみエラーが発生するようだ。
ネットで解決法を調べても簡単には出てこなかったので載せる。
結論から言うとRailsのCSRF対策が悪さをしていた。
目次
解決法
/etc/nginx/conf.d/default.conf に以下を追加。
nginxの設定ファイルは初期設定だとdefault.confだが、自分で変更していれば[アプリ名].confなどになっているのでそれを開く。
location @unicorn {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme; # この1行を追加
proxy_redirect off;
proxy_pass http://unicorn;
}
意味: httpヘッダーの情報にhttps($schemeに格納されている)をのせる
原因
RailsのCSRF対策がドメインが同じでもポートが違う(https,http)と働いてしまうからである。
CSRFというのはクロスサイトリクエストフォージェリの略で、不正なリクエストを外部から送らせる手法である。これの対策として、リクエストは自分のサイトからしか受け付けないようにするという方法がある。
Railsでは自動でCSRF対策がされているのだが、それがドメイン名のチェックだけでなくポートもチェックされるようになっている。これが今回のエラーの原因である。
大抵の場合、SSLはブラウザとウェブサーバ(Nginx)の間でおこなっており、内部のやりとりであるNginxとアプリケーションサーバ間の通信はhttpで行っている。
したがって、実際のリクエストがヘッダー情報(POST, https, example.com)であるのに対し、アプリケーション側が受け取るのは(POST, http, example.com)となる。ここで不一致が発生し、Railsは不正な投稿だと判断する。
そこで、アプリケーションが受け取るヘッダー情報をhttpsに書き換えて、Unicornに送る。
$schemeには元のリクエストのスキーム(https://example.comのhttpsの部分)が格納される。
では以下のように直接httpsを指定してはいけないのかというと、やらない方がいい。
proxy_set_header X-Forwarded-Proto https;
なぜなら、今度はhttpでリクエストされたときに不一致が発生するから。httpでのアクセスを全て禁止してhttpsのみにしてるならいいかもしれないが、わざわざ直接指定する意味はない。ただし、ロードバランサーを挟んでいるなど、$schemeでhttpsが取得できない状況ならアリ。
解決までの流れ
ログを色々チェック。Nginxの方は問題なし。Unicornの方を確認した。Unicornのエラーログにはsocketがどうたらと書かれていたがよくわからない。
ので、production.logの方を確認した。まず、問題のないGETをしてみてログの形式を見る。次にPOSTをしてエラーを起こす。
tail -150 production.log
等で変化があった部分を確認すると、POSTの文字の近くに
(HTTP Origin header (https://ronbato.com) didn't match request.base_url (http://ronbato.com)):
という文が。
ほう。SSL化をしたばっかだから怪しい。
この文でググった。
すると同じ人がいた。
解決。
まとめ・参考にしたサイト
単純にエラー文でググっただけでは出てこないことが多くなってきて、自分でログを調べる力がついてきた。
ググるだけだとどこかでつまづくからログを見る癖をつけた方がいい。
こういう記事を初めて書いたので、間違いなどあればコメントお願いします。
参考サイト
1件のピンバック
[part7] Basic認証+プレリリース – Rails開発の進め方 - 自由気ままにブログ書くンゴ