OpenID Connect Discovery
📍 このドキュメントの位置づけ
対象読者: OpenID Connect Discovery の実装詳細を理解したい開発者
このドキュメントで学べること:
- OpenID Connect Discovery の仕組み
.well-known/openid-configurationエンドポイントの実装- JWKS (JSON Web Key Set) エンドポイントの実装
- メタデータ生成の実装詳細
- AuthorizationServerConfiguration からのメタデータ抽出
前提知識:
- basic-15: OIDC Discovery & Dynamic Registrationの理解
- basic-12: OpenID Connect詳解の理解
- OAuth 2.0 / OIDC の基礎知識
🏗️ OpenID Connect Discovery とは
OpenID Connect Discovery は、認可サーバーのメタデータ(設定情報)をクライアントが自動的に取得できる仕組みです。
なぜDiscoveryが必要か
Discoveryなしの場合の問題点:
1. 開発者がドキュメントを読んでエンドポイントURLを手動設定
- authorization_endpoint: https://idp.example.com/authorize
- token_endpoint: https://idp.example.com/token
- userinfo_endpoint: https://idp.example.com/userinfo
- jwks_uri: https://idp.example.com/.well-known/jwks.json
2. サーバー側でURLが変更されると、すべてのクライアントで設定変更が必要
3. サポートされている機能(スコープ、アルゴリズム等)の確認が困難
Discoveryありの場合:
1. クライアントは .well-known/openid-configuration にアクセス
2. すべてのエンドポイントURL、サポート機能を自動取得
3. サーバー側の変更に自動追従
2つのエンドポイント
| エンドポイント | URL | 用途 |
|---|---|---|
| Server Configuration | /.well-known/openid-configuration | 認可サーバーのメタデータ |
| JWKS | /.well-known/jwks.json | 公開鍵セット(署名検証用) |
📋 実装アーキテクチャ
DiscoveryHandler
Discovery関連のリクエストを処理するハンドラーです。
public class DiscoveryHandler {
AuthorizationServerConfigurationQueryRepository
authorizationServerConfigurationQueryRepository;
/**
* .well-known/openid-configuration レスポンス生成
*/
public ServerConfigurationRequestResponse getConfiguration(Tenant tenant) {
// 1. AuthorizationServerConfiguration 取得
AuthorizationServerConfiguration authorizationServerConfiguration =
authorizationServerConfigurationQueryRepository.get(tenant);
// 2. レスポンス生成
ServerConfigurationResponseCreator serverConfigurationResponseCreator =
new ServerConfigurationResponseCreator(authorizationServerConfiguration);
Map<String, Object> content = serverConfigurationResponseCreator.create();
return new ServerConfigurationRequestResponse(
ServerConfigurationRequestStatus.OK, content);
}
/**
* .well-known/jwks.json レスポンス生成
*/
public JwksRequestResponse getJwks(Tenant tenant) {
// 1. AuthorizationServerConfiguration 取得
AuthorizationServerConfiguration authorizationServerConfiguration =
authorizationServerConfigurationQueryRepository.get(tenant);
// 2. JWKS レスポンス生成
JwksResponseCreator jwksResponseCreator =
new JwksResponseCreator(authorizationServerConfiguration);
Map<String, Object> content = jwksResponseCreator.create();
return new JwksRequestResponse(JwksRequestStatus.OK, content);
}
}
参考実装: DiscoveryHandler.java:30
🔧 Server Configuration 実装
ServerConfigurationResponseCreator
.well-known/openid-configuration のレスポンスを生成します。
public class ServerConfigurationResponseCreator {
AuthorizationServerConfiguration authorizationServerConfiguration;
public Map<String, Object> create() {
Map<String, Object> map = new HashMap<>();
// 1. 必須フィールド
map.put("issuer", authorizationServerConfiguration.issuer());
map.put("authorization_endpoint",
authorizationServerConfiguration.authorizationEndpoint());
map.put("jwks_uri", authorizationServerConfiguration.jwksUri());
map.put("response_types_supported",
authorizationServerConfiguration.responseTypesSupported());
map.put("subject_types_supported",
authorizationServerConfiguration.subjectTypesSupported());
map.put("id_token_signing_alg_values_supported",
authorizationServerConfiguration.idTokenSigningAlgValuesSupported());
// 2. オプションフィールド(存在する場合のみ追加)
if (authorizationServerConfiguration.hasTokenEndpoint()) {
map.put("token_endpoint",
authorizationServerConfiguration.tokenEndpoint());
}
if (authorizationServerConfiguration.hasUserinfoEndpoint()) {
map.put("userinfo_endpoint",
authorizationServerConfiguration.userinfoEndpoint());
}
if (authorizationServerConfiguration.hasRegistrationEndpoint()) {
map.put("registration_endpoint",
authorizationServerConfiguration.registrationEndpoint());
}
if (authorizationServerConfiguration.hasScopesSupported()) {
map.put("scopes_supported",
authorizationServerConfiguration.scopesSupported());
}
if (authorizationServerConfiguration.hasResponseModesSupported()) {
map.put("response_modes_supported",
authorizationServerConfiguration.responseModesSupported());
}
if (authorizationServerConfiguration.hasGrantTypesSupported()) {
map.put("grant_types_supported",
authorizationServerConfiguration.grantTypesSupported());
}
if (authorizationServerConfiguration.hasAcrValuesSupported()) {
map.put("acr_values_supported",
authorizationServerConfiguration.acrValuesSupported());
}
// 3. 暗号化関連(IDトークン)
if (authorizationServerConfiguration.hasIdTokenEncryptionAlgValuesSupported()) {
map.put("id_token_encryption_alg_values_supported",
authorizationServerConfiguration.idTokenEncryptionAlgValuesSupported());
}
if (authorizationServerConfiguration.hasIdTokenEncryptionEncValuesSupported()) {
map.put("id_token_encryption_enc_values_supported",
authorizationServerConfiguration.idTokenEncryptionEncValuesSupported());
}
// 4. 暗号化関連(Userinfo)
if (authorizationServerConfiguration.hasUserinfoSigningAlgValuesSupported()) {
map.put("userinfo_signing_alg_values_supported",
authorizationServerConfiguration.userinfoSigningAlgValuesSupported());
}
if (authorizationServerConfiguration.hasUserinfoEncryptionAlgValuesSupported()) {
map.put("userinfo_encryption_alg_values_supported",
authorizationServerConfiguration.userinfoEncryptionAlgValuesSupported());
}
// 5. Request Object 関連
if (authorizationServerConfiguration.hasRequestObjectSigningAlgValuesSupported()) {
map.put("request_object_signing_alg_values_supported",
authorizationServerConfiguration.requestObjectSigningAlgValuesSupported());
}
if (authorizationServerConfiguration.hasRequestObjectEncryptionAlgValuesSupported()) {
map.put("request_object_encryption_alg_values_supported",
authorizationServerConfiguration.requestObjectEncryptionAlgValuesSupported());
}
// 6. トークンエンドポイント認証
if (authorizationServerConfiguration.hasTokenEndpointAuthMethodsSupported()) {
map.put("token_endpoint_auth_methods_supported",
authorizationServerConfiguration.tokenEndpointAuthMethodsSupported());
}
if (authorizationServerConfiguration.hasTokenEndpointAuthSigningAlgValuesSupported()) {
map.put("token_endpoint_auth_signing_alg_values_supported",
authorizationServerConfiguration.tokenEndpointAuthSigningAlgValuesSupported());
}
// 7. Claims 関連
if (authorizationServerConfiguration.hasClaimsSupported()) {
map.put("claims_supported",
authorizationServerConfiguration.claimsSupported());
}
map.put("claims_parameter_supported",
authorizationServerConfiguration.claimsParameterSupported());
map.put("request_parameter_supported",
authorizationServerConfiguration.requestParameterSupported());
map.put("request_uri_parameter_supported",
authorizationServerConfiguration.requestUriParameterSupported());
map.put("require_request_uri_registration",
authorizationServerConfiguration.requireRequestUriRegistration());
// 8. mTLS 関連
map.put("tls_client_certificate_bound_access_tokens",
authorizationServerConfiguration.isTlsClientCertificateBoundAccessTokens());
if (authorizationServerConfiguration.hasMtlsEndpointAliases()) {
map.put("mtls_endpoint_aliases",
authorizationServerConfiguration.mtlsEndpointAliases());
}
// 9. Introspection / Revocation
if (authorizationServerConfiguration.hasIntrospectionEndpoint()) {
map.put("introspection_endpoint",
authorizationServerConfiguration.introspectionEndpoint());
}
if (authorizationServerConfiguration.hasRevocationEndpoint()) {
map.put("revocation_endpoint",
authorizationServerConfiguration.revocationEndpoint());
}
// 10. Authorization Details (RAR - RFC 9396)
if (!authorizationServerConfiguration.authorizationDetailsTypesSupported().isEmpty()) {
map.put("authorization_details_types_supported",
authorizationServerConfiguration.authorizationDetailsTypesSupported());
}
// 11. CIBA (Backchannel Authentication)
if (authorizationServerConfiguration.hasBackchannelTokenDeliveryModesSupported()) {
map.put("backchannel_token_delivery_modes_supported",
authorizationServerConfiguration.backchannelTokenDeliveryModesSupported());
}
if (authorizationServerConfiguration.hasBackchannelAuthenticationEndpoint()) {
map.put("backchannel_authentication_endpoint",
authorizationServerConfiguration.backchannelAuthenticationEndpoint());
}
// 12. Identity Assurance (IDA)
map.put("verified_claims_supported",
authorizationServerConfiguration.verifiedClaimsSupported());
if (authorizationServerConfiguration.verifiedClaimsSupported()) {
map.put("trust_frameworks_supported",
authorizationServerConfiguration.trustFrameworksSupported());
map.put("evidence_supported",
authorizationServerConfiguration.evidenceSupported());
map.put("id_documents_supported",
authorizationServerConfiguration.idDocumentsSupported());
map.put("id_documents_verification_methods_supported",
authorizationServerConfiguration.idDocumentsVerificationMethodsSupported());
map.put("claims_in_verified_claims_supported",
authorizationServerConfiguration.claimsInVerifiedClaimsSupported());
}
return map;
}
}
参考実装: ServerConfigurationResponseCreator.java:23
レスポンス例
{
"issuer": "https://idp.example.com",
"authorization_endpoint": "https://idp.example.com/authorize",
"token_endpoint": "https://idp.example.com/token",
"userinfo_endpoint": "https://idp.example.com/userinfo",
"jwks_uri": "https://idp.example.com/.well-known/jwks.json",
"registration_endpoint": "https://idp.example.com/register",
"scopes_supported": ["openid", "profile", "email", "phone", "address", "offline_access"],
"response_types_supported": ["code", "id_token", "token id_token", "code id_token", "code token", "code token id_token"],
"response_modes_supported": ["query", "fragment", "form_post", "jwt"],
"grant_types_supported": ["authorization_code", "implicit", "refresh_token", "client_credentials"],
"acr_values_supported": ["password", "fido-uaf", "webauthn"],
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["RS256", "ES256", "PS256"],
"id_token_encryption_alg_values_supported": ["RSA-OAEP", "RSA-OAEP-256"],
"id_token_encryption_enc_values_supported": ["A128CBC-HS256", "A256CBC-HS512"],
"userinfo_signing_alg_values_supported": ["RS256", "ES256"],
"request_object_signing_alg_values_supported": ["RS256", "ES256", "PS256"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt", "tls_client_auth"],
"token_endpoint_auth_signing_alg_values_supported": ["RS256", "ES256", "PS256"],
"claims_supported": ["sub", "name", "given_name", "family_name", "email", "email_verified", "phone_number"],
"claims_parameter_supported": true,
"request_parameter_supported": true,
"request_uri_parameter_supported": true,
"require_request_uri_registration": false,
"tls_client_certificate_bound_access_tokens": true,
"introspection_endpoint": "https://idp.example.com/introspect",
"revocation_endpoint": "https://idp.example.com/revoke",
"backchannel_token_delivery_modes_supported": ["poll", "ping"],
"backchannel_authentication_endpoint": "https://idp.example.com/bc-authorize",
"verified_claims_supported": true,
"trust_frameworks_supported": ["eidas", "jp_moj"],
"evidence_supported": ["id_document", "qes"],
"id_documents_supported": ["idcard", "passport", "driving_permit"],
"claims_in_verified_claims_supported": ["given_name", "family_name", "birthdate"]
}