LINE BOT サーバーを作るために、Spring Boot (Tomcat) だけで HTTPS 対応してみた

# TL, DR
- [ ] SSL 証明書を作成する
- [ ] Java の keystore に登録<br /> （`openssl pkcs12 -export -inkey <.keyファイル> -in <ダウンロードした.crtファイル> -out line-bot.pkcs12` で形式変換が必要)
- [ ] Spring-Boot の設定項目 `server.ssl.(enabled|key-store|key-store-password)` を通じて，<br />Embedded Tomcat に SSL 証明書を読み込ませる

# Motivation

LINE からメッセージ送受信のための [LINE Messaging API - JAVA SDK](https://github.com/line/line-bot-sdk-java) が公開されて[^bot]，
社外のプログラマーでも簡単に LINE 上の Chat Bot (LINE BOT) が作れるようになってきました．

SDK は，2016/10/02現在，[Java](https://github.com/line/line-bot-sdk-java) の他に [Go](https://github.com/line/line-bot-sdk-go), [Ruby](https://github.com/line/line-bot-sdk-ruby), [PHP](https://github.com/line/line-bot-sdk-php), [Perl](https://github.com/line/line-bot-sdk-perl) なんかがサポートされています．
開発者に必要なのは，SDK を利用して（必須では無いですが），
LINE のメッセージを受信する JSON API Server を作成して待ち受けさせておくことです．

その API (URL) を LINE に登録すると，LINE BOT にメッセージが届く度に URL がメッセージを引数に呼び出されます．

Java の Sample 実装である [sample-spring-boot-echo](https://github.com/line/line-bot-sdk-java/tree/master/sample-spring-boot-echo) を動かすためには、
起動したアプリケーションが，HTTPS でリクエストを受け付けられる状態にもっていく必要があります．
このために nginx を立てるのも煩雑だし，nginx の落とし忘れ，上げ忘れで余分な時間を取られるのもアレなので，nginx 等の外部ミドルウェア無しで https で待ち受ける方法を書いておきます．


[^bot]: 正確には，以前から公開されていましたが，2016年の9月に V2 API が公開されて，よりユーザーが簡単に API を利用できるようになりました．

# （ちなみに） そもそも LINE BOT SDK ってどんなことができるのか

以下の動画が詳しいです．両方日本語．メッセージのやり取りで終わるものについては，だいたいなんでもできます．

* 2016 LINE Developers Day
  * [Key Note](https://live.line.me/r/channels/31960/broadcast/52704)
  * [Live Coding Session](https://live.line.me/r/channels/31959/broadcast/52797)

# 本題 - nginx/Apache 無しで SSL 通信する

LINE のメッセージを受け取るサーバーは，LINE との通信に HTTPS を利用する必要があります．

実環境で，Java Web アプリが HTTPS 通信するためには，前段に nginx/apache を配置して，
そこで SSL を扱い，Tomcat では暗号化されていない http 通信として扱うことが一般的です．

ただ，開発のためにわざわざ nginx を立てると開発環境の構築に手間が増えることになります．
できれば `./gradle bootRun` すればどこでも http"S" で API Server が Listen してほしいわけです．

Java 用に HTTPS 暗号化キー・証明書を用意して，Spring-Boot の設定項目を利用する事で，
Spring-Boot(に含まれる Tomcat)のみで HTTPS 通信が可能になります．

## Tomcat が読み取れるような SSL 証明書を作成する

今回は， https://www.startssl.com/ を使って証明書を作成しました．

最終的に受け取れるファイルは， `*.crt` になります．

### 1. SSL の作成
コマンドプロンプトから，以下のように入力して，秘密鍵ファイル(.key) と，証明要求(.csr) を作成します．

ちなみに CSR と CRT がややこしいので気をつける必要があります． KEY + CSR から，CRT を作ってもらうイメージです．

```
openssl req -newkey rsa:2048 -keyout line-bot.key -out line-bot.csr
```

細かい事も聞かれますが，だいたい以下みたいな感じで埋めるところだけ埋めれば大丈夫です．

```
% openssl req -newkey rsa:2048 -keyout line-bot.key -out line-bot.csr
Generating a 2048 bit RSA private key
.......+++
.....................+++
writing new private key to 'line-bot.key'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Tokyo
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Ozaki Holdings
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:line-bot.serendipity.la
Email Address []:matsuda.kazuki@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
```

key password は忘れないようにメモっておきます．

### 2. `Free SSL Certificate – Class 1 DV SSL Certificate` を開いて必要事項を入力

`line-bot.csr` ファイルの中身（この中身は，秘密にしなくて大丈夫です）を Form に入力して適用にやっておきます．

```line-bot.csr
-----BEGIN CERTIFICATE REQUEST-----
MIICxzCCAa8CAQAwgYExCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEXMBUG
中略
N4D+6h1+Iw1yAWOLnntRoxfCt76SoLj1jYPnu3jwfXzzh4WQckdhodqO1g==
-----END CERTIFICATE REQUEST-----
```

> ![720bf208a52f7f0d82a23af6d9b318d8.png](https://qiita-image-store.s3.amazonaws.com/0/52956/382478ca-e8ef-b7e5-b3f3-5a67ae58d8cf.png)

完了するとすぐに CRT がダウンロード出来ます．

> ![3ebbe0d56af79eefa73c230d708df83d.png](https://qiita-image-store.s3.amazonaws.com/0/52956/4b565cd0-2e41-c332-b563-8d0ae17c9f6c.png)


### 3. .key と .crt を，Java の keystore に登録する

Start SSL でいくつかファイルができてきますが，nginx 用の CRT ファイルを使えば事が済むので，それを使います．

Tomcat から利用できるようにするためには，Java のキーストアに登録する必要があって，それが少しややこしいです．
openssl を使って，crt ファイルから，pkcs ファイルに変換します．

ここで .key の複合パスワードと，新しい pkcs の暗号化パスワードが必要になります．

```bash
openssl pkcs12 -export -inkey <.keyファイル> -in <ダウンロードした.crtファイル> -out line-bot.pkcs12
```

作成された pkcs12 ファイルを，java のキーストアに登録します．

```bash
% keytool -importkeystore -srckeystore line-bot.pkcs12 -srcstoretype PKCS12 -destkeystore ./line-bot.keystore
出力先キーストアのパスワードを入力してください:
新規パスワードを再入力してください:
ソース・キーストアのパスワードを入力してください:
別名1のエントリのインポートに成功しました．
インポート・コマンドが完了しました: 1件のエントリのインポートが成功しました．0件のエントリのインポートが失敗したか取り消されました
```

今度は，pkcs ファイルのパスワードと，新しく使う Java keystore のパスワードを入力する必要があります．

[^v1]: ちなみに，TLS 対応していないクライアントから Tomcat で接続を受け付ける場合，別途 SSLv3 有効化する作業が必要です．

ここまでで，

* .key ファイル（openssl で作成したもの）
* .csr ファイル(openssl で作成したもの)
* .crt ファイル(.csr ファイルを送りつけて作成されたものをダウンロード・解凍したもの)
* .pkcs12 ファイル（ダウンロードした .crt と、.key ファイルをまとめたもの）
* .keystore ファイル(.pkcs12 ファイルを，Java のキーストアに登録したもの)

ができました......

## Spring-Boot Embedded Tomcat に読み込ませる (sample-spring-boot-echo を例に)

設定ファイルで，keystore ファイルの場所と，パスワードを指定する必要があります．

クラスパスから読み取ることができるので，src/main/resource に入れておくと楽です．

keystore ファイルを resource フォルダにコピーしたら，Spring-Boot に設定を読み込ませるために，application-default.yml を作成しました．

```yaml:application-default.yml
server:
  ssl:
    enabled: true
    # SSL を有効に

    enabled-protocols: TLSv1.2
    # TLSv1.1 以下の全プロトコルを無効に

    key-store: classpath:line-bot.keystore
    # classpath 上の keystore ファイルに含まれた証明書を利用

    key-store-password: XXXXX
    # keystore の復号化キー

  port: 8443
    # ポート番号を 8443 に（任意）

line:
  bot:
    channel-token: # LINE's Channel Token Here
    channel-secret: # LINE's Channel Secret Here
```

`line-bot.keystore` と設定ファイルを `sample-spring-boot-echo` に追加した後での IDE 上の表示は次のようになります。

> ![f558e9e34d5be3cd904a56076a791f8e.png](https://qiita-image-store.s3.amazonaws.com/0/52956/8103faee-e808-71ef-01fc-2bacbca79f5e.png)

この状況で，Spring Boot を起動すると， HTTP/8080 ではなくて，HTTPS/8443 で Listen 開始したログが表示されるはずです．


```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.1.RELEASE)

前略

2016-10-02 17:54:49.689  INFO PID --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8443 (https)

中略

2016-10-02 17:54:50.834  INFO PID --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8443 (https)
2016-10-02 17:54:50.838  INFO PID --- [           main] c.e.bot.spring.echo.EchoApplication      : Started EchoApplication in 3.013 seconds (JVM running for 3.501)
```

https://localhost:8443 でアクセスして，ホスト名違いの警告が表示されるになっていれば，成功です．

> ![c4fca3e9db0cc5107c18cb43c3cb7c8b.png](https://qiita-image-store.s3.amazonaws.com/0/52956/817f73ba-edf6-86c0-6f83-6f97532762b2.png)

あとは，SSL 証明書を取得した Domain を IP に紐付けて，
`https://今回取得したドメイン:8443/callback` を LINE に登録すると，
無事に HTTPS で Message Callback が届き，Echo Bot として動き出します。

# 注