メインコンテンツまでスキップ

あなたの Java Spring Boot アプリケーションに認証 (Authentication) を追加する

このガイドでは、Logto をあなたの Java Spring Boot アプリケーションに統合する方法を示します。

ヒント:
  • このガイドのサンプルコードは、 spring-boot-sample GitHub リポジトリで見つけることができます。
  • あなたの Java Spring Boot アプリケーションに Logto を統合するために公式 SDK は必要ありません。 Spring SecuritySpring Security OAuth2 ライブラリを使用して、Logto との OIDC 認証 (Authentication) フローを処理します。

前提条件

  • Logto Cloud アカウントまたは セルフホスト Logto
  • サンプルコードは、Spring Boot の securing web starter を使用して作成されました。まだ Web アプリケーションを持っていない場合は、新しい Web アプリケーションをブートストラップするための指示に従ってください。
  • このガイドでは、 Spring SecuritySpring Security OAuth2 ライブラリを使用して、Logto との OIDC 認証 (Authentication) フローを処理します。概念を理解するために公式ドキュメントを確認してください。

あなたの Java Spring Boot アプリケーションを設定する

依存関係の追加

gradle ユーザーの場合、次の依存関係を build.gradle ファイルに追加します:

build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

maven ユーザーの場合、次の依存関係を pom.xml ファイルに追加します:

pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

OAuth2 クライアント設定

Logto コンソールで新しい Java Spring Boot アプリケーションを登録し、Web アプリケーションのクライアント資格情報と IdP 設定を取得します。

次の設定を application.properties ファイルに追加します:

application.properties
spring.security.oauth2.client.registration.logto.client-name=logto
spring.security.oauth2.client.registration.logto.client-id={{YOUR_CLIENT_ID}}
spring.security.oauth2.client.registration.logto.client-secret={{YOUR_CLIENT_ID}}
spring.security.oauth2.client.registration.logto.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.logto.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access
spring.security.oauth2.client.registration.logto.provider=logto

spring.security.oauth2.client.provider.logto.issuer-uri={{LOGTO_ENDPOINT}}/oidc
spring.security.oauth2.client.provider.logto.authorization-uri={{LOGTO_ENDPOINT}}/oidc/auth
spring.security.oauth2.client.provider.logto.jwk-set-uri={{LOGTO_ENDPOINT}}/oidc/jwks

実装

詳細に入る前に、エンドユーザーの体験について簡単に説明します。サインインプロセスは次のように簡略化できます:

  1. あなたのアプリがサインインメソッドを呼び出します。
  2. ユーザーは Logto のサインインページにリダイレクトされます。ネイティブアプリの場合、システムブラウザが開かれます。
  3. ユーザーがサインインし、あなたのアプリにリダイレクトされます(リダイレクト URI として設定されています)。

リダイレクトベースのサインインについて

  1. この認証 (Authentication) プロセスは OpenID Connect (OIDC) プロトコルに従い、Logto はユーザーのサインインを保護するために厳格なセキュリティ対策を講じています。
  2. 複数のアプリがある場合、同じアイデンティティプロバイダー (Logto) を使用できます。ユーザーがあるアプリにサインインすると、Logto は別のアプリにアクセスした際に自動的にサインインプロセスを完了します。

リダイレクトベースのサインインの理論と利点について詳しく知るには、Logto サインイン体験の説明を参照してください。


ユーザーがサインイン後にアプリケーションに戻るために、前のステップで client.registration.logto.redirect-uri プロパティを使用してリダイレクト URI を設定する必要があります。

リダイレクト URI を設定する

Logto Console のアプリケーション詳細ページに移動します。リダイレクト URI http://localhost:3000/callback を追加します。

Logto Console のリダイレクト URI

サインインと同様に、ユーザーは共有セッションからサインアウトするために Logto にリダイレクトされるべきです。完了したら、ユーザーをあなたのウェブサイトに戻すと良いでしょう。例えば、http://localhost:3000/ をサインアウト後のリダイレクト URI セクションとして追加します。

その後、「保存」をクリックして変更を保存します。

WebSecurityConfig を実装する

プロジェクトに新しいクラス WebSecurityConfig を作成する

WebSecurityConfig クラスは、アプリケーションのセキュリティ設定を構成するために使用されます。これは、認証 (Authentication) と認可 (Authorization) フローを処理する主要なクラスです。詳細については、 Spring Security ドキュメント を確認してください。

WebSecurityConfig.java
package com.example.securingweb;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@Configuration
@EnableWebSecurity

public class WebSecurityConfig {
// ...
}

idTokenDecoderFactory ビーンを作成する

Logto はデフォルトで ES384 を使用するため、同じアルゴリズムを使用するようにデフォルトの OidcIdTokenDecoderFactory を上書きする必要があります。

WebSecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;

public class WebSecurityConfig {
// ...

@Bean
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> SignatureAlgorithm.ES384);
return idTokenDecoderFactory;
}
}

ログイン成功イベントを処理するための LoginSuccessHandler クラスを作成する

ログイン成功後にユーザーを /user ページにリダイレクトします。

CustomSuccessHandler.java
package com.example.securingweb;

import java.io.IOException;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class CustomSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/user");
}
}

ログアウト成功イベントを処理するための LogoutSuccessHandler クラスを作成する

セッションをクリアし、ユーザーをホームページにリダイレクトします。

CustomLogoutHandler.java
package com.example.securingweb;

import java.io.IOException;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

public class CustomLogoutHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
HttpSession session = request.getSession();

if (session != null) {
session.invalidate();
}

response.sendRedirect("/home");
}
}

securityFilterChain を使用して WebSecurityConfig クラスを更新する

securityFilterChain は、受信リクエストとレスポンスを処理する責任を持つフィルタのチェーンです。

securityFilterChain を構成して、ホームページへのアクセスを許可し、他のすべてのリクエストに認証 (Authentication) を要求します。ログインとログアウトイベントを処理するために CustomSuccessHandlerCustomLogoutHandler を使用します。

WebSecurityConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;

public class WebSecurityConfig {
// ...

@Bean
public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/", "/home").permitAll() // ホームページへのアクセスを許可
.anyRequest().authenticated() // 他のすべてのリクエストには認証 (Authentication) が必要
)
.oauth2Login(oauth2Login ->
oauth2Login
.successHandler(new CustomSuccessHandler())
)
.logout(logout ->
logout
.logoutSuccessHandler(new CustomLogoutHandler())
);
return http.build();
}
}

ホームページを作成する

(プロジェクトにすでにホームページがある場合は、このステップをスキップできます)

HomeController.java
package com.example.securingweb;

import java.security.Principal;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
@GetMapping({ "/", "/home" })
public String home(Principal principal) {
return principal != null ? "redirect:/user" : "home";
}
}

このコントローラーは、ユーザーが認証 (Authentication) されている場合はユーザーページにリダイレクトし、そうでない場合はホームページを表示します。ホームページにサインインリンクを追加します。

resources/templates/home.html
<body>
<h1>Welcome!</h1>

<p><a th:href="@{/oauth2/authorization/logto}">Login with Logto</a></p>
</body>

ユーザーページを作成する

ユーザーページを処理するための新しいコントローラーを作成します:

UserController.java
package com.example.securingweb;

import java.security.Principal;
import java.util.Map;

import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UserController {

@GetMapping
public String user(Model model, Principal principal) {
if (principal instanceof OAuth2AuthenticationToken) {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) principal;
OAuth2User oauth2User = token.getPrincipal();
Map<String, Object> attributes = oauth2User.getAttributes();

model.addAttribute("username", attributes.get("username"));
model.addAttribute("email", attributes.get("email"));
model.addAttribute("sub", attributes.get("sub"));
}

return "user";
}
}

ユーザーが認証 (Authentication) されると、認証されたプリンシパルオブジェクトから OAuth2User データを取得します。詳細については、 OAuth2AuthenticationTokenOAuth2User を参照してください。

ユーザーデータを読み取り、 user.html テンプレートに渡します。

resources/templates/user.html
<body>
<h1>User Details</h1>
<div>
<p>
<div><strong>name:</strong> <span th:text="${username}"></span></div>
<div><strong>email:</strong> <span th:text="${email}"></span></div>
<div><strong>id:</strong> <span th:text="${sub}"></span></div>
</p>
</div>

<form th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
</form>
</body>

追加のクレーム (Claims) をリクエストする

principal (OAuth2AuthenticationToken) から返されるオブジェクトに一部のユーザー情報が欠けていることがあります。これは、OAuth 2.0 と OpenID Connect (OIDC) が最小特権の原則 (PoLP) に従うように設計されており、Logto はこれらの標準に基づいて構築されているためです。

デフォルトでは、限られたクレーム (Claim) が返されます。より多くの情報が必要な場合は、追加のスコープ (Scope) をリクエストして、より多くのクレーム (Claim) にアクセスできます。

備考:

「クレーム (Claim)」はサブジェクトについての主張であり、「スコープ (Scope)」はクレーム (Claim) のグループです。現在のケースでは、クレーム (Claim) はユーザーに関する情報の一部です。

スコープ - クレーム (Claim) 関係の非規範的な例を示します:

ヒント:

「sub」クレーム (Claim) は「サブジェクト (Subject)」を意味し、ユーザーの一意の識別子(つまり、ユーザー ID)です。

Logto SDK は常に 3 つのスコープ (Scope) をリクエストします:openidprofile、および offline_access

追加のユーザー情報を取得するには、 application.properties ファイルに追加のスコープを追加できます。例えば、 emailphone、および urn:logto:scope:organizations スコープをリクエストするには、次の行を application.properties ファイルに追加します:

application.properties
  spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,email,phone,urn:logto:scope:organizations

その後、 OAuth2User オブジェクトで追加のクレーム (Claims) にアクセスできます。

アプリケーションを実行してテストする

アプリケーションを実行し、 http://localhost:8080 にアクセスします。

  • サインインリンクがあるホームページが表示されます。
  • リンクをクリックして Logto でサインインします。
  • 認証 (Authentication) に成功すると、ユーザーページにリダイレクトされ、ユーザーの詳細が表示されます。
  • ログアウトボタンをクリックしてサインアウトします。ホームページにリダイレクトされます。

スコープとクレーム (Claims)

Logto は OIDC の スコープ (Scope) とクレーム (Claims) の規約 を使用して、ID トークンおよび OIDC userinfo エンドポイント からユーザー情報を取得するためのスコープ (Scope) とクレーム (Claims) を定義します。「スコープ (Scope)」と「クレーム (Claim)」は、OAuth 2.0 および OpenID Connect (OIDC) の仕様からの用語です。

簡単に言えば、スコープ (Scope) をリクエストすると、ユーザー情報に対応するクレーム (Claims) が取得されます。例えば、email スコープ (Scope) をリクエストすると、ユーザーの emailemail_verified データが取得されます。

サポートされているスコープと対応するクレーム (Claims) のリストはこちらです:

openid

クレーム名タイプ説明ユーザー情報が必要か?
substringユーザーの一意の識別子いいえ

profile

クレーム名タイプ説明ユーザー情報が必要か?
namestringユーザーのフルネームいいえ
usernamestringユーザーのユーザー名いいえ
picturestringエンドユーザーのプロフィール写真の URL。この URL は、画像を含む Web ページではなく、画像ファイル(例えば PNG、JPEG、または GIF 画像ファイル)を指す必要があります。この URL は、エンドユーザーを説明する際に表示するのに適したプロフィール写真を特に参照するべきであり、エンドユーザーが撮影した任意の写真を参照するべきではありません。いいえ
created_atnumberエンドユーザーが作成された時間。時間は Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。いいえ
updated_atnumberエンドユーザーの情報が最後に更新された時間。時間は Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。いいえ

その他の 標準クレーム には、family_namegiven_namemiddle_namenicknamepreferred_usernameprofilewebsitegenderbirthdatezoneinfo、および locale が含まれ、ユーザー情報エンドポイントを要求することなく profile スコープに含まれます。上記のクレームと異なる点は、これらのクレームは値が空でない場合にのみ返されることであり、上記のクレームは値が空の場合に null を返します。

注記:

標準クレームとは異なり、created_atupdated_at クレームは秒ではなくミリ秒を使用しています。

email

クレーム名タイプ説明ユーザー情報が必要か?
emailstringユーザーのメールアドレスいいえ
email_verifiedbooleanメールアドレスが確認済みかどうかいいえ

phone

クレーム名タイプ説明ユーザー情報が必要か?
phone_numberstringユーザーの電話番号いいえ
phone_number_verifiedboolean電話番号が確認済みかどうかいいえ

address

住所クレームの詳細については、OpenID Connect Core 1.0 を参照してください。

custom_data

クレーム名タイプ説明ユーザー情報が必要か?
custom_dataobjectユーザーのカスタムデータはい

identities

クレーム名タイプ説明ユーザー情報が必要か?
identitiesobjectユーザーのリンクされたアイデンティティはい
sso_identitiesarrayユーザーのリンクされた SSO アイデンティティはい

urn:logto:scope:organizations

クレーム名タイプ説明ユーザー情報が必要か?
organizationsstring[]ユーザーが所属する組織の IDいいえ
organization_dataobject[]ユーザーが所属する組織のデータはい

urn:logto:scope:organization_roles

クレーム名タイプ説明ユーザー情報が必要か?
organization_rolesstring[]ユーザーが所属する組織のロールで、<organization_id>:<role_name> の形式いいえ

パフォーマンスとデータサイズを考慮して、「ユーザー情報が必要か?」が「はい」の場合、クレームは ID トークンに表示されず、ユーザー情報エンドポイント のレスポンスで返されます。

追加のユーザー情報をリクエストするために、application.properties ファイルに追加のスコープ (Scope) とクレーム (Claims) を追加します。例えば、urn:logto:scope:organizations スコープ (Scope) をリクエストするには、次の行を application.properties ファイルに追加します:

application.properties
spring.security.oauth2.client.registration.logto.scope=openid,profile,offline_access,urn:logto:scope:organizations

ユーザーの組織 (Organization) クレーム (Claims) は、認可トークン (Authorization token) に含まれます。

さらなる読み物

エンドユーザーフロー:認証 (Authentication) フロー、アカウントフロー、組織フロー コネクターを設定する あなたの API を保護する