SpringSecurityでLINEログインを実現する
SpringBootで構築したWebアプリに対して、SpringSecurityによるLINEログインを実装した際の学び、及び備忘録です。
※SpringBootに関しては簡単に解説している部分もありますが、最低限の触れたことがある人向けの記事になります。
基礎知識の整理
まずは必要になる基礎知識を整理。
認証・認可周り
OAuth2.0
「認可」の仕組みを標準化したもの。
「認証」と「認可」の違いは、「認証がユーザーの本人確認をするもの」、「認可はアクセス制御などの権限管理を行うもの」という違いです。
例えば、とあるアプリがGoogleカレンダーにアクセスする許可を与えたりするのが認可。
OIDC(OpenID Connect)
OAuth2.0に認証の仕組みを追加した拡張仕様。 OIDCを使うことで、IDの連携ができるようになったり、SSOが実現できるようになる。
SSOについては前回の記事でも軽く触れているので、そちらを参照いただければと思います。
JWT(Json Web Token)
OAuth2.0やOIDCでは、トークンを検証することで認証や認可を行う。
OAuth2.0ではアクセストークン、OIDCではIDトークンを検証する。
アクセストークンはJWTが必須ではないが、JWTで表現することも多い。
IDトークンは必ずJWT形式である必要がある。
JWTの検証時に改ざんされていないことを保証するために、署名アルゴリズムを用いる。
この時のアルゴリズムの種類としてHS256(対象鍵方式:HMAC-SHA256)と、RS256(非対称鍵方式:RSA-SHA256)などがある。
Spring周り
SpringBoot
JavaやKotlinでWebアプリケーションを簡単に開発するためのフレームワーク。
SpringSecurity
SpringFrameworkで提供されている認証・認可の基盤。 予め多くの機能が提供されているため、認証認可に関するロジックを自前で実装することなく、ビジネスロジックの開発に注力できる。 提供されているインタフェースやクラスを拡張することで様々なカスタマイズが可能。
LINE周り
LINE Developers
LINEのサービスと自作のアプリケーションを連携させるためのAPIやツール、管理画面などを提供するポータルサイトのこと。連携できるサービスとして「LINE ログイン」「Messaging API」などがある。
LINE ログイン
LINEアカウントを使ってSSOを実現する技術。 LINE公式アカウント、LNE Messaging APIとの連携が簡単に実現できる。
Messaging APIと連携することで、LINEログイン時にLINE公式アカウントの友達追加を促すことができる。
プロバイダー
LINE Developersの中で組織やグループ、個人を表す単位。
LINE Developersのサービスを利用するには必ずプロバイダが必要。
個人のLINEアカウントでログインした場合は、そのアカウントがプロバイダになる。
チャネル
実際にLINEの機能を使うアプリケーションの単位。
「LINE ログイン」「Messaging API」など、連携する際はそれぞれのアプリ単位・サービス単位でチャネルを作成する必要がある。
環境の用意
SpringBootプロジェクトの作成
SpringBootのプロジェクトはSpring Initializrを使って作成します。
今回作成したプロジェクトの詳細は以下。
- Project:Maven
- Language:Kotlin
- SpringBoot 3.5.6
Metadataのnameなどは任意ですが、ここではlineLoginSampleにしました。
依存関係の追加
プロジェクト作成時に以下の依存関係を追加できるので、以下を追加。
- SoringWeb
- Thymeleaf
- Spring Security
- OAuth2 Client
※後から追加することも可能。
依存関係を追加したら、「GENERATE」ボタンをクリックしてプロジェクトをダウンロードします。
IDEで開く
IDEは任意ですが、私はIntelliJ IDEAを使用しました。
手動で依存関係の追加
プロジェクトを開いたら、pom.xml
に以下の依存関係を追加します。
※こちらの依存関係はSpring Initializrでは追加できなかったので、後から手動追加します。
※この依存関係がないと、LINEログイン時にJWTを処理できずにエラーになります。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
LINEログインのチャネル作成
続いてはLINE側の準備をします。
LINEログインを使用するには、 LINE Developsのサイトでチャネルを作成します。
LINE developersのページを開いてLINEアカウントでログイン。
コンソール画面のプロバイダーから、「新規チャネル作成」を選択。
「LINEログイン」を選択して、新しいLINEログインのチャネルを作成します。
チャネルの作成が終わったら、「チャネル基本設定」タブから、以下の値を控えておきます。
- チャネルID
- チャネルシークレット
また、「LINEログイン設定」タブから、「ウェブアプリでLINEログインを利用する」にチェックを入れる。
そして、コールバックURLにhttp://127.0.0.1:8080/login/oauth2/code/line
をします。
※今回はローカル開発で動作させるためにローカルのIPにしています。
ウェブアプリをサーバーにデプロイして使用する場合はIPの部分をサーバーのドメインに変更します。
実装
設定ファイル
SpringBootプロジェクトのsrc/main/resources/application.yaml
を作成し、以下の設定を追加します。
※デフォルトではapplication.properties
なので、リネームしてyamlに変更します。application.properties
のままでも対応可能ですが、重複する項目が多くなるのでyamlにする方が管理しやすくなります。
spring:
application:
name: lineLoginSample
security:
oauth2:
client:
provider:
line:
issuer-uri: https://access.line.me
registration:
line:
provider: line
client-id: xxxxxxxxxx
client-secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
client-authentication-method: client_secret_post
authorization-grant-type: authorization_code
redirect-uri: http://127.0.0.1:8080/login/oauth2/code/line
scope:
- profile
- openid
client-name: LINE
client-id
にはLINEログインのチャネルID、client-secret
にはLINEログインのチャネルシークレットを設定します。
ただし、ソースコードをバージョン管理する場合、シークレット情報がハードコーディングされた状態はまずいので、環境変数にするか、application-local.yaml
など、環境毎のファイルを用意してバージョン管理対象外にして扱うようにしましょう。
application-local.yaml
の例
spring:
security:
oauth2:
client:
registration:
line:
client-id: 1234567890
client-secret: 12345678901234567890123456789012
この場合、ベースの情報はapplication.yaml
が適用され、client-id
とclient-secret
の2項目がこのファイルの値で上書きされます。
このファイルを適用させるには、実行時の引数に--spring.profiles.active=local
を指定します。
環境変数にする場合は、以下のように${変数名}
とし、環境変数にあらかじめ設定するようにします。
client-id: ${LINE_LOGIN_CLIENT_ID}
client-secret: ${LINE_LOGIN_CLIENT_SECRET}
HTML
ログイン画面を必要最低限の実装で作成します。
LINEログインボタンの画像は以下のサイトよりダウンロードします。

LINEログインボタンを追加することによって、ユーザーがLINEログインを利用してアプリにログインできるようになります。img,[object Object]
https://developers.line.biz/ja/docs/line-login/login-button/line-login.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LINEログイン</title>
</head>
<body>
<div>
<h2>LINEログイン</h2>
<p th:if="${param.error}">ログイン失敗</p>
<div>
<a href="http://127.0.0.1:8080/oauth2/authorization/line">
<img id="btn-line" src="/images/line_login_btn.png">
</a>
</div>
</div>
</body>
</html>
ログインが成功した場合にリダイレクトする画面。
こちらも必要最低限で実装。
login-success.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ログイン成功</title>
</head>
<body>
<h2>ログイン成功</h2>
</body>
</html>
Config
SecurityConfig.kt
package com.example.lineloginsample
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.jose.jws.MacAlgorithm
import org.springframework.security.oauth2.jwt.JwtDecoderFactory
import org.springframework.security.web.SecurityFilterChain
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
.authorizeHttpRequests { auth ->
auth
.requestMatchers("/line-login", "/images/**").permitAll()
.anyRequest().authenticated()
}
.oauth2Login { oauth2 ->
oauth2
.loginPage("/line-login")
.failureUrl("/line-login?error")
.defaultSuccessUrl("/login-success", true)
}
return http.build()
}
@Bean
fun idTokenDecoderFactory(): JwtDecoderFactory<ClientRegistration?> {
val idTokenDecoderFactory = OidcIdTokenDecoderFactory()
idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
return idTokenDecoderFactory
}
}
コントローラ
LineController.kt
package com.example.lineloginsample
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
@Controller
class LineController {
@GetMapping("/line-login")
fun lineLogin(): String {
return "line-login"
}
@GetMapping("/login-success")
fun loginSuccess(): String {
return "login-success"
}
}
実装はこれで完了です。
SpringBootのアプリを起動して動かしてみます。
まずはhttp://localhost:8080/line-login
にアクセスします。
以下の画面が表示されました。

LINEのログインボタンをクリックすると、以下のようにLINEログイン画面にリダイレクトされます。

ここで、自分のLINEアカウントの情報を入力してログインボタンを押します。
ログイン成功の画面が表示されたので、うまくできていそうです。

やったー!
軽く解説
ログイン画面の解説
簡単に流れを解説。
LINEログインボタンをクリックしたときには以下のようなURLにリダイレクトすることで、LINEログイン画面へリダイレクトすることができます。
https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=1234567890&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%3Fkey%3Dvalue&state=12345abcde&scope=profile%20openid&nonce=09876xyz
一方、アプリ側ではLINEログインのボタンは以下のリンクを指定しています。
<a href="http://127.0.0.1:8080/oauth2/authorization/line">
これは、SpringSecurityの機能でうまくリダイレクトされるようになっているみたいです。application.yaml
でsecurity.oauth2
の設定をしておくことで、上記のURLにリクエストが投げられると、SpringSecurityが自動的にLINEの認可サーバーに対してリダイレクトされる仕組みです。
設定ファイルの解説
設定ファイルに関しても簡単に解説。
改めて、application.yaml
は以下。
spring:
application:
name: lineLoginSample
security:
oauth2:
client:
provider:
line:
issuer-uri: https://access.line.me
registration:
line:
provider: line
client-id: xxxxxxxxxx
client-secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
client-authentication-method: client_secret_post
authorization-grant-type: authorization_code
redirect-uri: http://127.0.0.1:8080/login/oauth2/code/line
scope:
- profile
- openid
client-name: LINE
issuer-uri
は、OIDCの設定においてよく出てくるキーワードで、OIDCプロバイダ(今回の場合はLINE)が公開している認証サーバーの起点となるURLを指します。
OIDCでは、認証やトークン検証のために、いくつかのエンドポイントが必要になります。issuer-uri
を指定すれば、それらのエンドポイントをまとめて自動解決してくれるが、個別で指定することも可能です。
# issuer-uri: https://access.line.me # 下記の3つをまとめて解決
authorization-uri: https://access.line.me/oauth2/v2.1/authorize # 認証用
token-uri: https://api.line.me/oauth2/v2.1/token # トークン発酵用
jwk-set-uri: https://api.line.me/oauth2/v2.1/certs # トークンが正しいか検証するための公開鍵配布場所
また、scope
では、profile
のみを指定した場合は、OAuth2のプロトコルが使用され、openid
を指定することで、OIDCが使用されるようになります。
LINEログインじにそれぞれのスコープで取得可能な情報はこちらを参照。
アルゴリズムの違い
SecurityConfig
に以下のBeanがあります。
@Bean
fun idTokenDecoderFactory(): JwtDecoderFactory<ClientRegistration?> {
val idTokenDecoderFactory = OidcIdTokenDecoderFactory()
idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
return idTokenDecoderFactory
}
これは、署名のアルゴリズムの差異を吸収するための設定。
SpringSecurityは、デフォルトでRS256になっているそう。
一方、LINEプラットフォームはWebアプリに対してはヘッダー情報にアルゴリズムとしてHS256が返されるとのこと。
参考:https://developers.line.biz/ja/docs/line-login/verify-id-token/#header
そのため、アルゴリズムとしてHS256が使用されるように設定することで、JWTの検証が正しく実行されます。
今回の例ではLINEログインのみを実装しようとしているため、この対応で問題ありませんが、複数のOIDCを実装する場合は、それぞれのプロバイダ事のアルゴリズムを確認して適切に対応する必要があります。
デバッグ時の苦労
実はSpringSecurityでLINEログインを実装する際に最も時間がかかったのが、このアルゴリズム差異の調整でした。
試しに、先ほどのBean(SecurityConfigクラスのidTokenDecoderFactory
メソッド)をコメントアウトして、LINEログインを試してみます。

ログインに失敗してログイン画面に戻ってきます。
しかし、アプリ側のコンソールには何のエラーログも出力されず、エラーの原因が何なのか全く分からず、調査が難航しました。
どうやら、SecurityConfigのfilterChain
の方で、認証に失敗した場合にはエラー内容が出力されるように調整する必要があったみたいです。
SecurityConfig.kt
.oauth2Login { oauth2 ->
oauth2
.loginPage("/line-login")
// .failureUrl("/line-login?error") // 元々のコード
// 以下でエラーが出力されるようにする
.failureHandler { request, response, exception ->
println("OAuth2 Login Failed: ${exception.message}")
exception.printStackTrace()
response.sendRedirect("/line-login?error")
}
.defaultSuccessUrl("/login-success", true)
}
この修正を行った後に再度LINEログインを試してみたところ、以下のようなエラーが出力されました。
OAuth2 Login Failed: [invalid_id_token] An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_id_token] An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.getJwt(OidcAuthorizationCodeAuthenticationProvider.java:251)
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.createOidcToken(OidcAuthorizationCodeAuthenticationProvider.java:238)
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.authenticate(OidcAuthorizationCodeAuthenticationProvider.java:156)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:200)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:239)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:229)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:198)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1776)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:975)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:493)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:1570)
Caused by: org.springframework.security.oauth2.jwt.BadJwtException: An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:188)
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.decode(NimbusJwtDecoder.java:142)
at org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider.getJwt(OidcAuthorizationCodeAuthenticationProvider.java:247)
... 73 more
Caused by: com.nimbusds.jose.proc.BadJOSEException: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:357)
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303)
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:162)
... 75 more
このエラーの内容から、SpringBootとLINEでアルゴリズムの違いが出ていることが分かり、それぞれの公式サイトを確認することで対応の方針を見極めることができました。
ログが出力される設定をしていくことは大事。
また、公式ドキュメントをきちんと読むことがやっぱり大事。
ログイン時の処理をカスタマイズ
ここまでの流れでSpringSecurityによるLINEログインが実現できましたが、実際のWebアプリケーションでは、ログイン時にユーザー情報を取得してDBに保存したり、画面上にユーザー情報を表示するなどの要件もあるかと思います。
DBへの保存はここでは扱いませんが、LINEのユーザー名を画面上に表示するまでのカスタマイズを行います。
まずは、ログインユーザーとして保持されるユーザーのクラスを作成します。
OIDCでのログインの場合、OidcUser
インターフェースを実装したクラスである必要があります。
今回はとりあえず必要最低限の情報だけ持つように。
LineOidcUser.kt
package com.example.lineloginsample
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.oauth2.core.oidc.OidcIdToken
import org.springframework.security.oauth2.core.oidc.OidcUserInfo
import org.springframework.security.oauth2.core.oidc.user.OidcUser
class LineOidcUser(
private val claims: Map<String?, Any?>?,
private val userInfo: OidcUserInfo?,
private val idToken: OidcIdToken,
private val authorities: Collection<GrantedAuthority?>?,
private val attributes: Map<String?, Any?>?,
private val nameAttributeKey: String,
): OidcUser {
override fun getClaims(): Map<String?, Any?>? {
return claims
}
override fun getUserInfo(): OidcUserInfo? {
return userInfo
}
override fun getIdToken(): OidcIdToken? {
return idToken
}
override fun getAttributes(): Map<String?, Any?>? {
return attributes
}
override fun getAuthorities(): Collection<GrantedAuthority?>? {
return authorities
}
override fun getName(): String? {
return attributes?.get(nameAttributeKey)?.toString()
}
fun getDisplayName(): String? {
return attributes?.get("name")?.toString()
}
}
次にログイン時に呼ばれるサービスクラスの実装。
デフォルトではOidcUserService
のloadUser
が呼ばれるようになっているので、継承してオーバーライドします。
DBにデータを保存する処理などが必要であれば、この中で実装してあげれば実現可能です。
LineOidcUserService.kt
package com.example.lineloginsample
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.stereotype.Service
@Service
class LineOidcUserService: OidcUserService() {
override fun loadUser(userRequest: OidcUserRequest): OidcUser {
val oidcUser = super.loadUser(userRequest)
return LineOidcUser(
claims = oidcUser.claims,
userInfo = oidcUser.userInfo,
idToken = oidcUser.idToken,
authorities = oidcUser.authorities,
attributes = oidcUser.attributes,
nameAttributeKey = "sub"
)
}
}
最後にログイン後の画面でログインしているユーザーの名前が表示されるようにします。
login-successs.html
<!DOCTYPE html>
<html lang="ja" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>ログイン成功</title>
</head>
<body>
<h2>ログイン成功</h2>
<p>ようこそ、<span sec:authentication="principal.displayName"></span> さん</p>
</body>
</html>
これで、ログイン後は以下のようにユーザー名が表示されるようになります。

参考

このページでは、OpenID Connectプロトコルをサポートし、ユーザー情報をIDトークンで取得できるLINEログインLINEログインを組み込めるアプリがない場合は、サンプルアプリを利用できます。「LINEログインを始めよう」を参照してください。
https://developers.line.biz/ja/docs/line-login/integrate-line-login/