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

# NginxとTomcatの設定
- Nginxで、Tomcatにproxy_passする直前に以下を追加。

`proxy_set_header   X-Forwarded-For $remote_addr`

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

```nginx: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`で指定している。

```nginx: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`の値を取得するようにする。

```groovy
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;`が利用される。
また、`httpPort`と`httpsPort`にはNginxの情報を記述しておく。
あとは`definition`にルールを記述してGrailsを再起動すればOK。

```groovy: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アドレスを取得する部分のコードを気をつけるという方針が一番簡単なのではと思います。