Edited at

[Grails]NginxとTomcatを連携&注意点

More than 3 years have passed since last update.


NginxとTomcatの設定


  • Nginxで、Tomcatにproxy_passする直前に以下を追加。

proxy_set_header X-Forwarded-For $remote_addr

自分の環境では、最終的に以下のようになっている。


my-grails-application.conf

# =====================================

# Grails settings
# =====================================

location @webapp {

proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;

proxy_pass http://127.0.0.1:8080;
proxy_cookie_path / /;
}


ちなみに上記はGrails用の設定のみを抜き出したNginx用のconf。

自分の環境では、少しドメイン名などの扱いが特種なので、Nginx自体が読み込むconfファイルは以下のように別途用意して、上記のgrails-application.confは、その中からincludeで指定している。


example.com.conf

# ==========================================

# Main settings.
# ==========================================
server {
listen 80;
server_name example.com;
root /usr/share/nginx/example_root_directory;

#charset koi8-r;
access_log /var/log/nginx/host.access.log;
error_log /var/log/nginx/host.error.log;

location / {
try_files $uri @webapp;
}
include /etc/nginx/conf.d/my-grails-application.conf;
}



  • Tomcatのserver.xmlのログ出力部分を以下のようにする。

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

prefix="localhost_access_log."
suffix=".txt"
pattern="%{X-Forwarded-For}i %h %l %u %t &quot;%r&quot; %s %b" />

%{X-Forwarded-For}iで、上記Nginxの設定で突っ込んだ$remote_addrの値、つまり実際にNginxにアクセスしてきたユーザのIPアドレスが参照できる。

%hには、Tomcatにアクセスしてきたユーザ、つまりNginxのIPアドレスが格納される。


アプリケーション側留意点


アクセスしてきたユーザのIPアドレス取得方法

基本的に当初の目的であるTomcatのアクセスログに実際にNginxにアクセスしてきたユーザのIPアドレスを記録するという点はクリアできたが、Grailsアプリケーション側でrequest.getRemoteAddr()を実行すると、NginxのIPアドレスが取得されてしまう。

なので、Grailsアプリケーション側でも以下のようにしてX-Forwarded-Forの値を取得するようにする。

class IndexController {

def index() {
// X-Forwarded-Forを取得。もしNginx側で指定されていない場合はしょうがないのでgetRemoteAddr()でお茶を濁す
String ip = request.getHeader("X-Forwarded-For")?: request.getRemoteAddr()
render "this is next page! your ip is ${ip}"
}
}

上記のIP取得ロジックは何処かユーティリティーメソッドなどを作成してそこで取得するようにしたほうが良い。


SpringSecurityCoreとの連携(SSL)

SpringSecurityCoreプラグインを利用して認証する管理画面があるとする。

例えばURLが/backend/**という場合は管理画面とする。

NginxにSSLを設定しておけば、ユーザ間とクライアント間はセキュアな通信になる。(当然)

ただし、注意すべき点として、NginxとTomcat間が非SSL(上記Nginxの設定proxy_pass http://127.0.0.1:8080;などがそう)の場合、Grails/SpringSecurityCoreから見ると当然スキーマはNginxから接続してきたhttpになる。

SpringSecurityCoreには、指定したURLは httpsアクセスのみ許可httpアクセスのみ許可どちらでもOK を指定するオプションがある。

httpsアクセスのみ許可しているURLにhttpでアクセスすると、自動的にhttps付のURLにリダイレクトしてくれてとても便利。

当然管理画面へのアクセスはSSLを強制したい。

しかし、デフォルトだとSpringSecurityCoreはNginxからのアクセスであるhttpというスキーマを使って確認する

つまりSpringSecurityCore的には、常にhttpのアクセス扱いになるので、httpsへ自動的にリダイレクト、しかしまたhttp扱いになるので再度httpsへリダイレクト...という流れを繰り返してリダイレクトループエラーがブラウザに表示されてしまう。

そこで、以下のようにuseHeaderCheckChannelSecurityで、HTTP Headerの中のX-Forwarded-Protoを参照してね、という設定をしてあげることでこの問題を回避できる。

この値はNginxで指定したX-Forwarded-Proto $scheme;が利用される。

また、httpPorthttpsPortにはNginxの情報を記述しておく。

あとはdefinitionにルールを記述してGrailsを再起動すればOK。


Config.groovyの末尾

environments {

production {
grails.plugin.springsecurity.auth.forceHttps = true
grails.plugin.springsecurity.portMapper.httpPort = 80
grails.plugin.springsecurity.portMapper.httpsPort = 443
grails.plugin.springsecurity.secureChannel.useHeaderCheckChannelSecurity = true
grails.plugin.springsecurity.secureChannel.definition = [
'/backend/**': 'REQUIRES_SECURE_CHANNEL'
]
}
}

ちなみに、上記のサンプルは本番環境で動作するときのみ有効になるように

environments {

production {
....
}
}

で囲んでいる。ココは各アプリケーションの開発環境に寄って柔軟に切り替えればOK。

この問題は、ロードバランサなどに直接Grailsアプリケーションが繋がっている場合などにも発生する。が、このような対応で回避できるはず。


その他

RemoteIpVavleを指定すればOKみたいな記事をよく見かけたけど、Grailsだからなのか不明だけど自分の確認した環境では確かにアクセスログにはユーザのグローバルIPアドレスが記録されるけど、Tomcat自体が403を返してページの閲覧が出来ないという本末転倒な自体に。。。

なので、今回のログ出力のパラメタと、Grailsアプリケーション側でユーザのグローバルIPアドレスを取得する部分のコードを気をつけるという方針が一番簡単なのではと思います。