Spring Boot + Spring Security で OAuth2.0 / OpenID Connect を実装する【仕組みから理解する認証・認可】

AWS

「OAuth2.0 と OpenID Connect は何が違うのか」「Spring Security がどこまで自動でやってくれるのか」——この2点で混乱している方は多いのではないでしょうか。

本記事では、Spring Boot + Spring Security を使った実装を通じて、OAuth2.0(認可フレームワーク)と OpenID Connect(認証プロトコル)それぞれの役割を明確に区別しながら解説します。あわせて、AWS Cognito を IdP として利用するための Tips もご紹介します。

対象読者は Spring Boot の基礎を理解しており、認証・認可の仕組みを深く学びたいバックエンドエンジニアです。


全体構成図で把握する

Spring Security + OAuth2.0 + OpenID Connect 全体構成図

上の図は、Spring Boot アプリケーションが OAuth2.0 / OpenID Connect を利用する際の全体像です。登場人物を整理すると次のようになります。

  • ブラウザ(エンドユーザー):アプリにアクセスする主体
  • Spring Boot Application:OAuth2 Client と Resource Server の両役割を担う
  • Authorization Server(IdP):認可コード・Access Token・ID Token を発行する
  • OIDC 専用エンドポイント:UserInfo エンドポイントおよびディスカバリーエンドポイントを提供(OpenID Connect の追加機能)

図の下部にある「プロトコル役割の整理」がこの記事の核心です。OAuth2.0 は「認可コードフロー・Access Token・scope による権限管理」を担い、OpenID Connect はその上に「ID Token によるユーザー認証・UserInfo エンドポイント」を追加します。


OAuth2.0 と OpenID Connect の違いを整理する

OAuth2.0(認可)とOpenID Connect(認証)の役割比較図

最も重要な前提として、OAuth2.0 は「認可(Authorization)」のフレームワークであり、OpenID Connect は「認証(Authentication)」のプロトコルです。OAuth2.0 単体では「このユーザーが誰なのか」を証明する手段がありません。

項目OAuth2.0OpenID Connect
目的認可(何をさせてよいか)認証(誰であるか)
主なトークンAccess TokenID Token(JWT)
有効化条件任意の scopescope に openid を含める
Spring クラスOAuth2User / JwtAuthenticationTokenOidcUser / OidcIdToken
エンドポイント/oauth2/authorize, /oauth2/token/userinfo, /.well-known/openid-configuration

ポイント:scope に openid を追加するだけで、認可サーバーは ID Token を発行し、Spring Security は自動的に OIDC モードで動作します。既存の OAuth2 設定に1単語追加するだけで認証機能が有効化されるのが OIDC の特徴です。


プロジェクトのセットアップ

Spring Initializr で以下の依存関係を追加します。OAuth2 Client は認可コードフローとログイン処理(OIDC 含む)を担い、OAuth2 Resource Server は JWT 検証による API 保護を担います。

<!-- pom.xml -->
<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Security (OAuth2 Client + Resource Server 両方含む)-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
</dependencies>


application.yml の設定(OAuth2 Client + OIDC)

Spring Boot の自動設定を最大限に活用します。issuer-uri を指定すると、Spring Security がディスカバリーエンドポイント(/.well-known/openid-configuration)を参照し、認可エンドポイント・トークンエンドポイント・JWKS URI を自動取得します。これはOpenID Connect のディスカバリー機能です。

spring:
  security:
    oauth2:
      # --- OAuth2 Client (ログイン + OIDC) の設定 ---
      client:
        registration:
          my-idp:                          # 任意のクライアント登録ID
            client-id: your-client-id
            client-secret: your-client-secret
            # scope に openid を含めることで OIDC が有効化される
            # profile, email は OIDC の追加クレーム取得用
            scope:
              - openid      # 【OIDC】 ID Token 発行のトリガー
              - profile     # 【OIDC】 name, picture 等のクレーム
              - email       # 【OIDC】 email クレーム
            authorization-grant-type: authorization_code  # 【OAuth2.0】
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          my-idp:
            # issuer-uri を指定するだけで全エンドポイントが自動設定される
            # これは OIDC ディスカバリーの機能
            issuer-uri: https://your-idp.example.com

      # --- Resource Server (API 保護 + JWT 検証) の設定 ---
      resourceserver:
        jwt:
          # 【OAuth2.0】 JWK Set URI で署名検証用公開鍵を取得
          jwk-set-uri: https://your-idp.example.com/.well-known/jwks.json
          # issuer-uri でも代替可能(自動で jwks_uri を取得)
          # issuer-uri: https://your-idp.example.com


SecurityFilterChain の設定

Spring Security 6 以降は SecurityFilterChain を Bean として定義する方式が標準です。oauth2Login() が OAuth2 Client(認可コードフロー + OIDC)を有効化し、oauth2ResourceServer() が Resource Server(JWT 検証)を有効化します。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/", "/public/**").permitAll()   // 公開ページ
                .anyRequest().authenticated()                     // それ以外は認証必須
            )
            /*
             * 【OAuth2.0 + OIDC】 oauth2Login() を有効化することで以下が自動設定される:
             *   - OAuth2LoginAuthenticationFilter(認可コード受け取り)
             *   - AuthorizationRequestRedirectFilter(認可サーバーへのリダイレクト)
             *   - OidcUserService(ID Token の検証 + UserInfo 取得)<- OIDC 専用
             */
            .oauth2Login(oauth2 -> oauth2
                .defaultSuccessUrl("/dashboard", true)
            )
            /*
             * 【OAuth2.0】 oauth2ResourceServer() を有効化することで以下が自動設定される:
             *   - BearerTokenAuthenticationFilter(Authorization ヘッダーから JWT 取り出し)
             *   - JwtAuthenticationProvider(JwtDecoder で JWT を検証)
             *   - JwtAuthenticationConverter(JWT クレームを権限に変換)
             */
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults())
            );

        return http.build();
    }
}

oauth2Login() と oauth2ResourceServer() の使い分け:前者はブラウザベースのログインフロー(セッション管理・リダイレクト)を担い、後者は REST API へのリクエストに付与された Bearer トークンの検証を担います。Web アプリと API の両方を提供する場合は、2つの SecurityFilterChain を定義して役割を分けるか、1つの FilterChain に両方を共存させます。


認証フロー詳細(Authorization Code + OIDC)

Spring Security Authorization Code + OIDC 認証フロー図

左のフロー(①〜⑥)が OAuth2 Client のログインフロー、右のフローが Resource Server の JWT 検証フローです。それぞれの処理をコードレベルで確認しましょう。

  1. ①アクセス:未認証ユーザーが /protected にアクセス
  2. ②リダイレクトAuthorizationRequestRedirectFilter が認可サーバーへリダイレクト(OAuth2.0
  3. ③認可コード返却:ユーザーが認証・同意後、コールバック URL に認可コードが届く(OAuth2.0
  4. ④トークン取得OAuth2LoginAuthenticationFilter がサーバー間通信でトークンを取得。scope に openid があれば ID Token も返却される(OAuth2.0 + OIDC
  5. ⑤ID Token 検証 + UserInfo 取得OidcUserService が ID Token の署名・クレームを検証し、UserInfo エンドポイントからプロフィールを取得。OidcUser を生成(OIDC 専用処理
  6. ⑥SecurityContext 保存:認証済み OidcUser を SecurityContext に格納。以降のリクエストはセッションで認証状態を維持


コントローラーでユーザー情報を取得する

認証完了後は @AuthenticationPrincipal アノテーションで OidcUser を取得できます。OidcUser は ID Token のクレームと UserInfo エンドポイントの情報を統合したオブジェクトです。これはOpenID Connect の機能です。

@RestController
public class UserController {

    /**
     * 【OIDC】 OidcUser はID TokenとUserInfoエンドポイントの情報を統合したオブジェクト
     * scope=openid を指定している場合のみ OidcUser として取得可能
     */
    @GetMapping("/me")
    public Map<String, Object> currentUser(
            @AuthenticationPrincipal OidcUser oidcUser) {

        return Map.of(
            // 【OIDC】 ID Token のクレーム(sub = ユーザーの一意ID)
            "sub",   oidcUser.getSubject(),
            "email", oidcUser.getEmail(),
            "name",  oidcUser.getFullName(),

            // 【OIDC】 ID Token 全体を取得
            "idToken", oidcUser.getIdToken().getTokenValue(),

            // 【OAuth2.0】 権限情報
            "authorities", oidcUser.getAuthorities()
        );
    }

    /**
     * 【OAuth2.0】 Resource Server として動作するエンドポイント
     * Authorization: Bearer <JWT> ヘッダーで呼び出す
     * JwtAuthenticationToken はAccess TokenのJWT情報を保持
     */
    @GetMapping("/api/resource")
    public Map<String, Object> protectedResource(
            @AuthenticationPrincipal Jwt jwt) {

        return Map.of(
            // 【OAuth2.0】 JWT クレーム(Access Token のペイロード)
            "sub",    jwt.getSubject(),
            "scope",  jwt.getClaimAsStringList("scope"),
            "issuer", jwt.getIssuer()
        );
    }
}


Resource Server の JWT 検証をカスタマイズする

Resource Server の JWT 検証(OAuth2.0 の機能)では、デフォルトで署名・有効期限(exp)・発行者(iss)・受信者(aud)が検証されます。aud クレームの検証を追加する場合は JwtDecoder をカスタム Bean として定義します。

@Configuration
public class JwtConfig {

    /**
     * 【OAuth2.0】 JwtDecoder をカスタマイズして aud クレームを検証
     * spring.security.oauth2.resourceserver.jwt.issuer-uri を参照して
     * JWKS URI を自動取得するため、issuer-uri の設定が必要
     */
    @Bean
    public JwtDecoder jwtDecoder(
            @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") String issuerUri) {

        NimbusJwtDecoder decoder = JwtDecoders.fromIssuerLocation(issuerUri);

        // カスタムバリデーター: aud クレームに my-api が含まれることを確認
        OAuth2TokenValidator<Jwt> audienceValidator =
            jwt -> jwt.getAudience().contains("my-api")
                ? OAuth2TokenValidatorResult.success()
                : OAuth2TokenValidatorResult.failure(
                    new OAuth2Error("invalid_token", "Invalid audience", null));

        // デフォルトの検証(iss, exp)にカスタム検証を追加
        OAuth2TokenValidator<Jwt> combinedValidator =
            new DelegatingOAuth2TokenValidator<>(
                JwtValidators.createDefaultWithIssuer(issuerUri),
                audienceValidator
            );

        decoder.setJwtValidator(combinedValidator);
        return decoder;
    }

    /**
     * 【OAuth2.0】 JWT クレームから権限(GrantedAuthority)へのマッピングをカスタマイズ
     * デフォルトは scope クレームを SCOPE_ プレフィックス付きで権限化する
     */
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
        converter.setAuthorityPrefix("ROLE_");       // プレフィックスを変更
        converter.setAuthoritiesClaimName("roles");  // クレーム名を変更(デフォルトは scope)

        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
        return jwtConverter;
    }
}


OIDC の UserInfo エンドポイントと OidcUser の仕組み

Spring Security が OIDC 対応の IdP に接続する際、以下の処理を自動で行います。これらはすべてOpenID Connect の機能です。

  1. ID Token の検証OidcIdTokenDecoderFactory が ID Token(JWT)の署名・nonce・iss・aud を検証
  2. UserInfo エンドポイント呼び出しOidcUserService が Access Token を使って /userinfo を呼び出し、追加クレームを取得
  3. OidcUser の生成:ID Token のクレームと UserInfo レスポンスをマージして DefaultOidcUser を生成

UserInfo の呼び出しをカスタマイズする場合は OidcUserService を Bean として定義します。

@Configuration
public class OidcConfig {

    /**
     * 【OIDC専用】 OidcUserService をカスタマイズ
     * デフォルトでは scope=openid 時に UserInfo エンドポイントを自動呼び出し
     * 追加で取得したいスコープ(groups, custom_claim 等)を指定可能
     */
    @Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        OidcUserService delegate = new OidcUserService();

        // UserInfo エンドポイントから追加で取得するスコープを指定
        delegate.setAccessibleScopes(Set.of("profile", "email", "groups"));

        return userRequest -> {
            // 標準の OidcUser 生成処理を実行
            OidcUser oidcUser = delegate.loadUser(userRequest);

            // 独自クレームからロールを生成する例
            Set<GrantedAuthority> authorities = new HashSet<>(oidcUser.getAuthorities());
            List<String> groups = oidcUser.getClaimAsStringList("groups");
            if (groups != null) {
                groups.stream()
                    .map(g -> new SimpleGrantedAuthority("ROLE_" + g.toUpperCase()))
                    .forEach(authorities::add);
            }

            return new DefaultOidcUser(
                authorities,
                oidcUser.getIdToken(),
                oidcUser.getUserInfo()
            );
        };
    }
}


AWS Cognito を IdP として利用する Tips

AWS Cognito は OAuth2.0 / OpenID Connect に対応したマネージドサービスです。前述の Authorization Server と OIDC エンドポイントをすべて提供するため、Spring Boot アプリの設定変更はほぼ不要です。

spring:
  security:
    oauth2:
      client:
        registration:
          cognito:
            client-id: YOUR_COGNITO_APP_CLIENT_ID
            client-secret: YOUR_COGNITO_APP_CLIENT_SECRET
            scope:
              - openid
              - email
              - profile
        provider:
          cognito:
            # リージョンと User Pool ID を変更するだけ
            issuer-uri: https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXX

      resourceserver:
        jwt:
          jwk-set-uri: https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_XXXXXXXX/.well-known/jwks.json

Cognito 特有の注意点を以下にまとめます。

  • userNameAttributeName の設定:Cognito は UserInfo レスポンスのユーザー名属性が cognito:username または username になります。provider.cognito.user-name-attribute: username を設定しないと認証失敗することがあります。
  • RP-Initiated Logout 非対応:Cognito は OpenID Connect の RP-Initiated Logout 仕様を完全には実装していません。ログアウト時には https://DOMAIN.auth.ap-northeast-1.amazoncognito.com/logout へのカスタムリダイレクトが必要です。
  • aud クレームの違い:Cognito が発行する Access Token の aud クレームは User Pool の client_id ではなく Cognito 独自の値になることがあります。JwtDecoder のバリデーターを調整する必要があります。
  • issuer-uri 一つで完結issuer-uri を指定するだけで、認可エンドポイント・トークンエンドポイント・JWKS URI・UserInfo エンドポイントがすべて自動設定されます。

Cognito の OIDC ディスカバリーを確認する:ブラウザで https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/openid-configuration にアクセスすると、Cognito が提供する全エンドポイントの一覧を JSON 形式で確認できます。Spring Security はこの URL を自動取得して設定に使用します。


まとめ:OAuth2.0 と OIDC の役割を Spring Security のコードで見る

処理Spring Security クラスプロトコル
認可サーバーへのリダイレクトAuthorizationRequestRedirectFilterOAuth2.0
認可コードの受け取りOAuth2LoginAuthenticationFilterOAuth2.0
トークンエンドポイント呼び出しDefaultAuthorizationCodeTokenResponseClientOAuth2.0
ID Token の検証OidcIdTokenDecoderFactoryOIDC(専用)
UserInfo エンドポイント呼び出しOidcUserServiceOIDC(専用)
ユーザー情報の保持OidcUser / DefaultOidcUserOIDC(専用)
Bearer トークンの抽出BearerTokenAuthenticationFilterOAuth2.0
JWT の署名・クレーム検証JwtDecoder / NimbusJwtDecoderOAuth2.0
JWT からの権限マッピングJwtAuthenticationConverterOAuth2.0

Spring Security は OAuth2.0 と OpenID Connect を深く統合しており、設定ベースで多くの処理を自動化します。重要なのは「oauth2Login() で OIDC ログインフローが有効化され、oauth2ResourceServer() で JWT 検証による API 保護が有効化される」という役割の分担を理解することです。scope に openid を含めた瞬間から、Spring Security は自動的に OIDC モードに切り替わり ID Token の検証と OidcUser の生成を行います。


参考リソース

コメント

タイトルとURLをコピーしました