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>


実装に入る前に

実装コードに入る前に、「なぜ scope に openid を書くと挙動が変わるのか」
「ログイン用途と外部APIアクセス用途でどう設計が違うのか」を整理しておくと
コードの意味が理解しやすくなります。

404 NOT FOUND | ITリテラシー拡大ブログ
ITをもっと身近に、もっと楽しく、もっと便利に


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の基礎・用語を整理したい方
認可の仕組みとして利用されるOAuth 2.0とは?用語・フローをわかりやすく解説

OIDCとID Tokenの仕組みを整理したい方
OpenID Connectとは?その仕組みと必要な知識とフローをまとめて解説

Authorization Code Grantの詳細とClient Credentials Grantとの使い分けはこちら。
サービス間通信の設計にも役立ちます。
Authorization Code GrantとClient Credentials Grantの違いと使い分け

AWS CognitoをIdPとして使う構成の全体像はこちら。
認証・認可の仕組みを完全理解|OAuth2.0・OpenID Connect・AWS Cognitoを初心者から実務レベルまで解説


まとめ: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をコピーしました