目的
LINE SNS k認証機能を実装し、LINE認証の仕組みと機能実装方法を理解すること
構築環境と必要なもの
- Mac(windowsでも可能)
- Terraformコマンドを叩けること
- LINEアカウント
- AWSアカウント
どう作るか
構成の説明
AWS環境で構築・公開を行う
ECSへデプロイする
LINE認証Callbackドメインの登録は ALB の DNS(Aレコード)を登録する
内部ドメイン
- /
- トップページを表示
- /login
- login認証ドメインへリダイレクトする
- /callback
- LINE認証後にリクエストされるドメイン
-
/
へリダイレクトする
大まかな手順
- Webアプリケーションを開発
- アプリケーションのImageを作成
- ImageをECRへPush
- インフラ環境を構築 & アプリケーションをデプロイ
- LINE CHANNELを作成 & CallBackURLを登録
- 動作確認
開発
-
Webアプリケーションを開発
以下のRepositoryをcloneすると早い
https://github.com/GitEngHar/SnsAuthenticationApp/pull/1- 認証用 フロントエンド
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello</title> </head> <body> <p>HelloWorld</p> <!-- リダイレクト機能確認用URL--> <a th:href="@{/redirect}">リダイレクト機能テスト</a><br> <a th:href="@{/login}">ログイン</a> </body> </html>
- トップページを表示するController
package com.example.snsauthenticationapp.web; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; /** Topページ*/ @Controller @RequestMapping("/") public class IndexController { /** トップページ */ @GetMapping("") public String index(Model model) { return "index"; } /** リダイレクト確認用実装 */ @GetMapping("redirect") public String redirect() { return "redirect:/"; } }
- 認証リダイレクト処理 と CallBack処理
package com.example.snsauthenticationapp.web; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.view.RedirectView; /** * LINE認証リダイレクトURLへリダイレクト * CallBackハンドリング * */ @Controller public class LoginController { final LineAuthenticationService lineAuthenticationService; public LoginController(LineAuthenticationService lineAuthenticationService){ this.lineAuthenticationService = lineAuthenticationService; } /** LINE認証用リダイレクト */ @GetMapping("/login") public RedirectView login() { String redirectUrl = lineAuthenticationService.createRedirectURL(); return new RedirectView(redirectUrl); } /** CallBack処理 */ @RequestMapping(value = "/callback", method = RequestMethod.GET) public String getToken(@RequestParam("code") String code, HttpServletRequest req, HttpServletResponse res) throws Exception{ return "redirect:/"; } }
- LINE認証用 プロパティ をオブジェクトへ代入する
package com.example.snsauthenticationapp.domain; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 認可コードの取得 * アクセストークンの発行 / ユーザー情報を取得 * */ @Component @ConfigurationProperties(prefix = "lineauth") public class LineAuthElement { private String responseType; private String redirectUri; private String clientId; private String scope; private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } public String getResponseType() { return responseType; } public void setResponseType(String responseType) { this.responseType = responseType; } public String getRedirectUri() { return redirectUri; } public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } }
- 認証ドメインの生成
package com.example.snsauthenticationapp.web; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.apache.http.client.utils.URIBuilder; import org.apache.commons.lang3.RandomStringUtils; import com.example.snsauthenticationapp.domain.LineAuthElement; /** * state, nonce用 検証ランダム文字列の生成 * アクセストークンの検証や破棄 * */ @Service public class LineAuthenticationService { private final LineAuthElement lineAuthElement; public LineAuthenticationService(LineAuthElement lineAuthElement) { this.lineAuthElement = lineAuthElement; } /** リダイレクト先のドメインを生成する * 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 * */ public String createRedirectURL() { try { // csrf検証用 stringを生成 lineAuthElement.setState(createUniqueState()); // 認証用リダイレクトパラメータを生成 List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("response_type", lineAuthElement.getResponseType())); params.add(new BasicNameValuePair("client_id", lineAuthElement.getClientId())); params.add(new BasicNameValuePair("redirect_uri", lineAuthElement.getRedirectUri())); params.add(new BasicNameValuePair("scope", lineAuthElement.getScope())); params.add(new BasicNameValuePair("state", lineAuthElement.getState())); // 認証用ドメインを生成 URIBuilder builder = new URIBuilder(); builder.setScheme("https"); builder.setHost("access.line.me"); builder.setPath("oauth2/v2.1/authorize"); // 認証用ドメインにパラメータを付与 builder.setParameters(params); // リダイレクトURIを生成 return builder.build().toURL().toString(); } catch (Exception e) { System.out.println(e.getMessage()); return null; } } private static String createUniqueState(){ return RandomStringUtils.randomAlphanumeric(20); } }
- 認証用property
spring: application: name: "SnsAuthenticationApp" lineauth: response_type: "code" client_id: ${CLIENT_ID} redirect_uri: ${REDIRECT_URI} # state scope: "profile" # nonce # prompt # max_age # ui_locales # bot_prompt # initial_amr_display # switch_amr # disable_auto_login # disable_ios_auto_login # code_challenge # code_challenge_method
-
アプリケーションのImageを作成
JarのBuild IntelliJ~/Documents/SnsAuthenticationApp $ ./gradlew build
Jarの出力先
作業場所/
リポジトリ名
/build/libs~/Documents/SnsAuthenticationApp/build/libs
JarでWebAppが動作することを確認
# jarを起動 java -jar SnsAuthenticationApp-0.0.1-SNAPSHOT.jar # 起動ログ . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.4.1) 2025-01-19T10:22:06.301+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] c.e.s.SnsAuthenticationAppApplication : Starting SnsAuthenticationAppApplication v0.0.1-SNAPSHOT using Java 23.0.1 with PID 41855 (/Users/harukisugiyama/Documents/SnsAuthenticationApp/build/libs/SnsAuthenticationApp-0.0.1-SNAPSHOT.jar started by harukisugiyama in /Users/harukisugiyama/Documents/SnsAuthenticationApp/build/libs) 2025-01-19T10:22:06.302+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] c.e.s.SnsAuthenticationAppApplication : No active profile set, falling back to 1 default profile: "default" 2025-01-19T10:22:06.572+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-01-19T10:22:06.577+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-01-19T10:22:06.577+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.34] 2025-01-19T10:22:06.588+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-01-19T10:22:06.589+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 270 ms 2025-01-19T10:22:06.624+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index 2025-01-19T10:22:06.708+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2025-01-19T10:22:06.715+09:00 INFO 41855 --- [SnsAuthenticationApp] [ main] c.e.s.SnsAuthenticationAppApplication : Started SnsAuthenticationAppApplication in 0.538 seconds (process running for 0.702) 2025-01-19T10:22:15.885+09:00 INFO 41855 --- [SnsAuthenticationApp] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2025-01-19T10:22:15.885+09:00 INFO 41855 --- [SnsAuthenticationApp] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2025-01-19T10:22:15.886+09:00 INFO 41855 --- [SnsAuthenticationApp] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
動作確認
# localにcurlする ~/Documents/SnsAuthenticationApp/build/libs ❯ curl localhost:8080 # 出力 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello</title> </head> <body> <p>HelloWorld</p> </body> </html>
DockerFileの作成
# Java build Base Image FROM eclipse-temurin # 作業ディレクトリを設定 WORKDIR /app # Buildするjarをコピー COPY ./app/SnsAuthenticationApp-0.0.1-SNAPSHOT.jar app.jar # 環境変数を設定(検証用) # ENV REDIRECT_URI=hogepoge # portを設定 EXPOSE 8080 # jarの起動 ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Build
docker build -t sbs-authentication-app . --platform linux/amd64
-
インフラ環境を構築 & アプリケーションをデプロイ
- Repositoryをcloneする
https://github.com/GitEngHar/TfSnsAuthenticationApp - Repository内に terraform.tfvars を作成
-
aws_account_id = "1234"
のように環境変数を設定する
-
-
terraform apply
でリソースを構築
- Repositoryをcloneする
-
LINE CHANNELを作成 & CallBackURLを登録
-
動作確認 (ALB DNS(Aレコード) を取得して設定)
- 自分のLINEアカウントが表示されて認証後 リダイレクト先にアクセスできたら成功
苦労したこと
検証する際にBuildImageのアーキテクチャと実行アーキテクチャの差でエラー
- ビルドしたImageでコンテナを立てるためにMacに適した platformを指定してimageをbuildする必要があった
-
amd64
でアーキテクチャを指定して Imageを構築する
-
実際のエラー
JRE version: (21.0.5+11) (build )
2025-01-19T01:27:39.572762094Z # Java VM: OpenJDK 64-Bit Server VM (21.0.5+11-LTS, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-aarch64)
2025-01-19T01:27:39.572762844Z # Problematic frame:
2025-01-19T01:27:39.572763469Z # j java.lang.System.registerNatives()V+0 java.base@21.0.5
2025-01-19T01:27:39.572764094Z #
2025-01-19T01:27:39.572764594Z # No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
2025-01-19T01:27:39.572765427Z #
2025-01-19T01:27:39.573900719Z # An error report file with more information is saved as:
2025-01-19T01:27:39.573961052Z # /app/hs_err_pid1.log
2025-01-19T01:27:39.577807927Z [0.024s][warning][os] Loading hsdis library failed
2025-01-19T01:27:39.580877844Z #
2025-01-19T01:27:39.580902261Z # The crash happened outside the Java Virtual Machine in native code.
2025-01-19T01:27:39.580904302Z # See problematic frame for where to report the bug.
2025-01-19T01:27:39.580905594Z #
2025-01-19T01:27:39.580906636Z
2025-01-19T01:27:39.580907886Z [error occurred during error reporting (), id 0x5, SIGTRAP (0x5) at pc=0x0000ffffadf57ebc]
解決
ビルド時に アーキテクチャ (--platform linux/amd64
) を指定
docker build -t spring-docker . --platform linux/amd64