OIDCについて

今回は、OIDCについて簡単に見ていきたいと思います。

OIDCとは

  • OIDCは「OpenID Connect」の略称で、認証のための開放型の標準プロトコルです。
  • OAuth 2.0プロトコルの上に構築された身元認証レイヤーです。
  • ウェブベースのアプリケーション、モバイルアプリ、JavaScript クライアントに、セキュアで相互運用可能な認証方法を提供します。
  • ID トークンという概念を使用します。
  • JSONウェブトークン(JWT)形式でエンコードされた情報を含みます。

主な特徴

  • シングルサインオン(SSO)の実現
  • セキュアなAPI認証
  • クレーム(ユーザー情報)の標準化された交換

メリット

  • 実装が比較的容易
  • 広く採用されている標準規格
  • セキュリティとプライバシーの向上

用途

  • 企業の社内システム認証
  • ソーシャルログイン(Google、Facebook等との連携)
  • クラウドサービスの認証

OIDCは現代のウェブアプリケーションやサービスにおいて、セキュアで効率的な認証を実現するために広く使用されています。

サンプル

サンプルコードはReact、Javap+Springです。サンプルのため、全てのプロジェクト構造は含まれていません。また、コードサンプルはSpring BootバックエンドとReactフロントエンドを使用したOIDC認証の基本的な実装となります。

Java

  • Spring Security と OAuth2 Clientを使用してOIDC認証を設定します。
  • GoogleをOIDCプロバイダとして設定しています(他のプロバイダも同様に設定可能)。
  • 認証されたユーザー情報を取得するためのエンドポイント(/user)を提供します。

build.gradle

必要な依存関係をGradleファイルに追加します。

plugins {
    id 'org.springframework.boot' version '2.6.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    
    // 必要に応じて、以下の依存関係を追加できます
    // implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    // implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

test {
    useJUnitPlatform()
}
  • spring-boot-starter-web: Web
    • アプリケーション開発に必要な依存関係
  • spring-boot-starter-oauth2-client
    • OAuth2/OIDC クライアント機能を提供
  • spring-boot-starter-security
    • Spring Security 機能を提供

SecurityConfig

SecurityConfigクラスでOAuth2ログインを設定します。

package com.example.demo;

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.web.SecurityFilterChain;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Value("${spring.security.oauth2.client.registration.google.client-id}")
    private String googleClientId;

    @Value("${spring.application.name}")
    private String appName;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(a -> a
                .antMatchers("/", "/error", "/webjars/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .oidcUserService(this.oidcUserService())
                )
            )
            .logout(l -> l
                .logoutSuccessUrl("/")
                .permitAll()
            );
        return http.build();
    }

    @Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        final OidcUserService delegate = new OidcUserService();

        return (userRequest) -> {
            OidcUser oidcUser = delegate.loadUser(userRequest);
            
            // ここでユーザー情報を処理したり、カスタムクレームを追加したりできます
            
            return oidcUser;
        };
    }
}

.oauth2Login(oauth2 -> ...)

  • これは OAuth 2.0 / OIDC ログインを有効にするメソッドです。
  • ラムダ式を使用して、OAuth 2.0 ログインの詳細な設定を行います。

oauth2.userInfoEndpoint(userInfo -> ...)

  • userInfoEndpoint() は、認証後にユーザー情報を取得するエンドポイントの設定を行います。
  • OIDC プロバイダ(例:Google)のユーザー情報エンドポイントからデータを取得する方法を指定します。

userInfo.oidcUserService(this.oidcUserService())

  • oidcUserService() は、OIDC プロバイダから取得したユーザー情報を処理するサービスを指定します。
  • this.oidcUserService() は、この設定クラス

Controller

UserControllerでユーザー情報を提供するエンドポイントを実装します。

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.Map;

@RestController
public class UserController {

    @GetMapping("/user")
    public Map<String, Object> user(@AuthenticationPrincipal OAuth2User principal) {
        return Collections.singletonMap("name", principal.getAttribute("name"));
    }
}

application.yml

application.ymlにGoogleのクライアントIDとシークレットを設定します(環境変数を使用)。

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}

React

  • ユーザーの認証状態に基づいて、ログインボタンまたはウェルカムメッセージを表示します。
  • バックエンドの「/user」エンドポイントを呼び出してユーザー情報を取得します。
  • ログイン処理はバックエンドにリダイレクトすることで行います。

App.js

function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    axios.get('/user')
      .then(response => setUser(response.data))
      .catch(error => console.error('Error fetching user', error));
  }, []);

  const login = () => {
    window.location.href = '/oauth2/authorization/google';
  };

  const logout = () => {
    axios.post('/logout')
      .then(() => setUser(null))
      .catch(error => console.error('Error logging out', error));
  };

  if (!user) {
    return <button onClick={login}>Login with Google</button>;
  }

  return (
    <div>
      <h1>Welcome, {user.name}!</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

package.json

{
  "name": "oidc-react-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "axios": "^0.24.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build"
  },
  "proxy": "http://localhost:8080"
}
  • Reactアプリケーションを作成し、必要な依存関係(axios等)をインストールします。
  • App.jsで認証状態の管理とユーザー情報の取得ロジックを実装します。
  • プロキシ設定をpackage.jsonに追加し、APIリクエストをバックエンドに転送します。

コメントを残す