LINE BOTアプリとLIFFを利用して既存WEBサービスをログインレス化する part.1

# はじめに
LINEのミニアプリを今回作成しました。LINE BOTアプリとLIFFを組み合わせてログインレスなシステムとなっています。
特にSpring-bootを利用したLINE BOTアプリ作成に関する記事が少なかったので、備忘録を兼ねて今回検証用に作成したサンプルプログラムの解説を行います。

↓サンプルプログラム

https://github.com/aka-ao/spring-boot-app-with-line-bot
## メリット
- LINEとの連携を行えばログインなしで既存のWEBサービスを利用できるようになるためユーザビリティの向上が狙える
- LINE上でサービスを展開するので、アプリのインストールが不要
- 特定のユーザにメッセージを送信することも可能なので、属性情報等にあったメッセージを個別に送信できる

## LINE Messaging APIとは
各サービスとLINEユーザがLINEのメッセージを通じてやり取りを行うためのAPI。
テキストのメッセージだけでなく、スタンプ、画像、位置情報をやり取りすることも可能。
また、友達登録などのイベントを受信することも可能。
これらのやり取りはLINE側で受信したメッセージやイベントを指定したサーバに対してコールバックする形でやり取りが行われる。

https://developers.line.biz/ja/services/messaging-api/

## LIFFとは
LINE Front-end Framework（LIFF）は、LINEが提供するウェブアプリのプラットフォーム。
LIFFを利用することでLINEのユーザ情報の取得などができる。

https://developers.line.biz/ja/docs/liff/overview/

# 環境
- Java 11
- Spring boot 2.5.4
- line-bot-spring-boot 4.5.0
- LIFF 2.16.1

# 解説範囲
1. チャンネル設定(本記事)
2. LINE BOT SDK導入(本記事)
3. ユーザ連携(本記事)  
WEBサービス上のユーザIDとLINEのユーザIDの紐付けを安全に行う。
4. ログインレス機能の実現(次回解説)  

# 1. チャンネル設定
今回の機能を実装するためには同じプロバイダーの中にLINE Messaging APIとLIFF用のチャンネル2つを用意する必要がある。
## 1.1. LINE Messaging API
下記の公式リファレンスを参照して作成。

https://developers.line.biz/ja/docs/messaging-api/getting-started/

## 1.2. LIFF用チャンネル
こちらも下記の公式リファレンスを参照して作成。

https://developers.line.biz/ja/docs/liff/getting-started/#creating-provider-and-channel

## 2. LINE BOT SDK導入
各言語のAPIが用意されてる。今回はJavaのSDKを利用。Githubにサンプルプログラム含めて公開されている。

https://developers.line.biz/ja/docs/messaging-api/line-bot-sdk/

https://github.com/line/line-bot-sdk-java

### 2.1. pom.xmlに書き加える

```xml
<dependency>
  <groupId>com.linecorp.bot</groupId>
  <artifactId>line-bot-spring-boot</artifactId>
  <version>4.5.0</version>
</dependency>
```

### 2.2. application.propertiesに設定を追加
  - channel-token及びchannel-secretは1.1.で設定したチャンネルのコンソール画面から取得可能。

```
line.bot.channel-token=""
line.bot.channel-secret=
line.bot.handler.path=/callback
```

## 3. ユーザ連携
下記リンクにLINEが提示しているユーザ連携フローがあるのでそのとおりに実装を行う。大まかな流れは以下の通り。

- link tokenをLINEから取得する
- 連携用のログインページを用意し、取得したlink tokenをクエリパラメータに設定してユーザに連携する。
- ユーザにはWEBサービスにログインをしてもらう。
- ログインが成功したらnonceを生成しログインIDとnonceをセットにしてDBに保存する。
- ログイン成功ページに遷移する。
- ログイン成功ページには連携用リンクを埋め込む。
- 連携用リンクにアクセスするとLINEからサーバに対してアカウント連携完了のリクエストが飛んでくる。
- リクエストの中にはnonceとLINE IDが含まれているので先程DBに保存したレコードと紐付ける。


![LINE連携](https://developers.line.biz/assets/img/sequence.63cc462d.png)
画像は下記より引用

https://developers.line.biz/ja/docs/messaging-api/linking-accounts/

### 3.1. イベントハンドラ実装
- @LineMessageHandlerのアノテーションをクラスに書くとLINEのチャンネルのトーク画面にてユーザがメッセージを送信したときにメッセージの内容をLINEからのコールバックという形で受け取れる。
- @EventMappingのアノテーションをメソッドに書くと引数の型のメッセージを受け取れる。handleTextMessageEventメソッドの場合はテキストのメッセージを受信した場合にこの関数が呼ばれる仕組みになる。
    - event.getMessage().getText()とするとメッセージのテキストの内容が取得できる。
    - このメソッドの場合は"connect"というメッセージでは連携に必要なLinkTokenを取得する。
    - 取得したLinkTokenをログインURLのクエリパラメータに含める形でユーザに連携する。

```java
@LineMessageHandler
public class LineConnectHandler {

    @EventMapping
    public Message handleTextMessageEvent(MessageEvent<TextMessageContent> event) throws ExecutionException, InterruptedException {
        Message res;
        switch (event.getMessage().getText()) {
            case "connect":
                String userId = event.getSource().getUserId();
                IssueLinkTokenResponse response = lineMessagingClient.issueLinkToken(userId).join();
                String linkToken = response.getLinkToken();
                String loginUrl = serverUrl + "/login?linkToken=" + linkToken;
                res = new TextMessage(loginUrl);
                break;
            default:
                res = new TextMessage(event.getMessage().getText());
        }
        return res;
    }
}
```

- 上記の実装を行うと「connect」というメッセージを送信後下記のようなレスポンスを取得できる。リンクを踏むとログインページに遷移しログイン処理を行う。（ユーザ認証関連の実装はGithub参照）

<img width="400" alt="037a2319-163c-9fa4-1029-44ef3f65a0a0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226241/edc2328d-433a-6bc4-19e7-49aa550577ac.jpeg">


### 3.2. ログイン成功後
- ログインページにアクセスした際にクエリパラメータから取得したlink tokenをセッションに格納しておき、ログイン成功時にはセッションから取り出す
- ログイン成功後はnonceを生成。
  - nonceの生成についてはFintanを参考
  - [安全でランダムな文字列の生成方法を比較する](https://fintan.jp/?p=5036)
- 連携用リンクを生成する。生成したリンクをログイン後の遷移先のHTMLに埋め込む。
    - リンクURLは以下のように決まっているのでそのとおりにする。
    - `https://access.line.me/dialog/bot/accountLink?linkToken={link token}&nonce={nonce}`

```java
	@RequestMapping("/login")
	public String login(
			@RequestParam(name = "linkToken", required = false) String linkToken,
			Model model,
			HttpSession session
	) {
		session.setAttribute("linkToken", linkToken);
		return "login";
	}

	@RequestMapping(value = {"/", "/hello"})
	private String init(@RequestHeader() String accessToken, Model model, HttpSession session) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		//Principalからログインユーザの情報を取得
		String userName = auth.getName();
		model.addAttribute("userName", userName);
		String linkToken = session.getAttribute("linkToken").toString();
		String nonce = UUID.randomUUID().toString();
		String linkUrlStr = "https://access.line.me/dialog/bot/accountLink?linkToken=" + linkToken + "&nonce=" + nonce;

		// ユーザIDとnonceをセットにして保存
		mapper.insertLine(userName, nonce);
		model.addAttribute("linkUrl", linkUrlStr);
		return "hello";
	}
```

ログイン後のページから上記のコントローラで生成したリンクを踏むと下記のような画面に遷移する。
<img width="250" alt="037a2319-163c-9fa4-1029-44ef3f65a0a0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226241/55c3672d-eaea-9ca7-106d-3b436c164de3.png">

### 3.3. アカウント連携
- 上記のアカウント連携画面が表示されるとLINEからアカウント連携イベントを受信できる。
- LineConnectHandlerクラスに下記のコードを追加してユーザIDとLINE IDを紐付ける。
  - 引数のeventにはLINE IDと先程のリンクに設定したnonceが含まれている
  - nonceをキーにしてDBのレコードを更新することでユーザIDとLINE IDの紐付けが完了する。

```java
    @EventMapping
    public Message handleAccountLinkEvent(AccountLinkEvent event) {
        String lineId = event.getSource().getUserId();
        String nonce = event.getLink().getNonce();
        // LINE IDとユーザIDの紐付けを行う。
        mapper.connectUser(lineId, nonce);
        // LINE IDをキーにしてユーザ名を取得することが可能になった。
        String userName = mapper.getUserNameByLineId(lineId);
        return new TextMessage("userName: " + userName + ", userId(LINE): " + lineId);
    }
```
最終的に下記のようなメッセージをLINEアプリで取得することができる。
<img width="400" alt="037a2319-163c-9fa4-1029-44ef3f65a0a0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226241/f30fe859-dfe7-f77e-2769-641368f161f8.jpeg">

# 次回
LINEとの連携まで今回解説しました。
次回は連携したユーザ情報を元にLINEの認証認可機能を使ってLIFF上でログインレスなシステムを実現します。

# 免責
この記事にはセキュリティに関する内容が含まれています。
最新の注意を払って記事を執筆していますが、この記事によって起こった損害については一切保証しかねます。
この記事を参考にし同様の実装を行う際には改めて問題はないか検討し、自己責任で適応していただきますようお願い致します。