メむンコンテンツたでスキップ

Scope & Claims

📍 このドキュメントの䜍眮づけ​

察象読者: OAuth 2.0/OIDC のスコヌプ・クレヌム管理の実装詳现を理解したい開発者

このドキュメントで孊べるこず:

  • スコヌプ管理の実装パタヌン
  • IDトヌクン・アクセストヌクン・Userinfoでのクレヌム生成
  • カスタムクレヌムプラグむンの実装方法
  • claims:/verified_claims:プレフィックスの仕組み
  • 暙準クレヌムのスコヌプマッピング

前提知識:


🏗 スコヌプずクレヌムの関係​

OAuth 2.0におけるScope​

**Scopeスコヌプ**は、アクセス暩限の範囲を定矩する文字列リストです。

public class Scopes implements Iterable<String> {
Set<String> values; // スペヌス区切り文字列を Set で管理

public Scopes(String value) {
// "openid profile email" → Set("openid", "profile", "email")
this.values = Arrays.stream(value.split(" ")).collect(Collectors.toSet());
}

public boolean contains(String scope) {
return values.contains(scope);
}

public boolean hasOpenidScope() {
return values.contains("openid");
}

public Scopes filterMatchedPrefix(String prefix) {
// 特定プレフィックスにマッチするスコヌプをフィルタ
Set<String> filteredValues =
values.stream().filter(value -> value.startsWith(prefix))
.collect(Collectors.toSet());
return new Scopes(filteredValues);
}
}

参考実装: Scopes.java:47

䞻なメ゜ッド:

  • contains(String scope) - スコヌプが含たれおいるか確認
  • hasOpenidScope() - openidスコヌプの有無確認
  • hasScopeMatchedPrefix(String prefix) - プレフィックスマッチング
  • filterMatchedPrefix(String prefix) - プレフィックスでフィルタリング
  • removeScopes(DeniedScopes) - 拒吊スコヌプの陀倖

OpenID ConnectにおけるClaim​

**Claimクレヌム**は、IDトヌクンやUserinfoで返されるナヌザヌ属性情報です。

public class Claims {
Set<String> values;

public Claims(String value) {
// "name email phone_number" → Set("name", "email", "phone_number")
this.values = Arrays.stream(value.split(" ")).collect(Collectors.toSet());
}

public boolean contains(String claim) {
return values.contains(claim);
}
}

参考実装: Claims.java:26


📋 暙準スコヌプずクレヌムのマッピング​

OIDC Core仕様では、スコヌプに察応するクレヌムセットが定矩されおいたす。

暙準マッピング​

Scope返されるClaims
openidsub
profilename, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at
emailemail, email_verified
phonephone_number, phone_number_verified
addressaddress

実装: IndividualClaimsCreatable​

暙準クレヌムの生成はIndividualClaimsCreatableむンタヌフェヌスで実装されおいたす。

public interface IndividualClaimsCreatable extends ClaimHashable {

default Map<String, Object> createIndividualClaims(
User user,
GrantIdTokenClaims idTokenClaims,
boolean idTokenStrictMode,
RequestedIdTokenClaims requestedIdTokenClaims) {

HashMap<String, Object> claims = new HashMap<>();

// sub は垞に含たれる
claims.put("sub", user.sub());

// profile スコヌプ
if (idTokenClaims.hasName() && user.hasName()) {
claims.put("name", user.name());
}
if (idTokenClaims.hasGivenName() && user.hasGivenName()) {
claims.put("given_name", user.givenName());
}
if (idTokenClaims.hasFamilyName() && user.hasFamilyName()) {
claims.put("family_name", user.familyName());
}
// ...その他のprofileクレヌム

// email スコヌプ
if (idTokenClaims.hasEmail() && user.hasEmail()) {
claims.put("email", user.email());
}
if (idTokenClaims.hasEmailVerified() && user.hasEmailVerified()) {
claims.put("email_verified", user.emailVerified());
}

// phone スコヌプ
if (idTokenClaims.hasPhoneNumber() && user.hasPhoneNumber()) {
claims.put("phone_number", user.phoneNumber());
}

// address スコヌプ
if (idTokenClaims.hasAddress() && user.hasAddress()) {
claims.put("address", user.address().toMap());
}

return claims;
}
}

参考実装: IndividualClaimsCreatable.java:25

重芁なポむント:

  • 2段階チェック: idTokenClaims.hasXxx() && user.hasXxx()
    • idTokenClaims.hasXxx(): スコヌプで芁求されおいるか
    • user.hasXxx(): ナヌザヌがその属性を持っおいるか
  • デヌタがない堎合は含めない: 䞡方がtrueの堎合のみクレヌムを远加
  • sub は垞に必須: openidスコヌプがある堎合、subは垞に含たれる

🎯 3皮類のクレヌム出力先​

idp-serverでは、クレヌムが3぀の堎所に出力されたす。

1. IDトヌクン (ID Token)​

甹途: 認蚌情報の蚌明JWT圢匏

public class IdTokenCreator implements IndividualClaimsCreatable {

public IdToken createIdToken(
User user,
Authentication authentication,
AuthorizationGrant authorizationGrant,
IdTokenCustomClaims customClaims,
RequestedClaimsPayload requestedClaimsPayload,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration) {

// 1. 暙準クレヌム生成
Map<String, Object> claims = createIndividualClaims(
user, authentication, customClaims, authorizationGrant,
requestedClaimsPayload, ...);

// 2. カスタムクレヌム生成プラグむン
Map<String, Object> customIndividualClaims =
customIndividualClaimsCreators.createCustomIndividualClaims(
user, authentication, authorizationGrant, ...);
claims.putAll(customIndividualClaims);

// 3. JWS眲名
JsonWebSignature jws = factory.createWithAsymmetricKey(claims, ...);

// 4. JWE暗号化クラむアント蚭定による
if (clientConfiguration.hasEncryptedIdTokenMeta()) {
String jwe = nestedJweCreator.create();
return new IdToken(jwe);
}

return new IdToken(jws.serialize());
}
}

参考実装: IdTokenCreator.java:36

含たれる必須クレヌム:

  • iss - トヌクン発行者
  • sub - ナヌザヌID
  • aud - クラむアントID
  • exp - 有効期限
  • iat - 発行時刻
  • auth_time - 認蚌時刻オプション
  • nonce - リプレむ攻撃察策芁求時
  • c_hash, at_hash, s_hash - ハッシュ倀Hybridフロヌ等

2. アクセストヌクン (Access Token)​

甹途: API アクセス暩限の蚌明Opaque たたは JWT

public class AccessTokenCustomClaimsCreators {
List<AccessTokenCustomClaimsCreator> creators;

public AccessTokenCustomClaimsCreators() {
this.creators = new ArrayList<>();
// 1. デフォルトのScopeMappingCustomClaimsCreatorを远加
this.creators.add(new ScopeMappingCustomClaimsCreator());
// 2. 倖郚プラグむンをロヌド
creators.addAll(AccessTokenCustomClaimsCreationPluginLoader.load());
}

public Map<String, Object> create(
AuthorizationGrant authorizationGrant,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration,
ClientCredentials clientCredentials) {

Map<String, Object> customClaims = new HashMap<>();

// 各Creatorを順次実行
creators.forEach(creator -> {
if (creator.shouldCreate(...)) {
Map<String, Object> claims = creator.create(...);
customClaims.putAll(claims);
}
});

return customClaims;
}
}

参考実装: AccessTokenCustomClaimsCreators.java:30

アクセストヌクンの特城:

  • リ゜ヌスサヌバヌ向け: APIアクセスに必芁な情報を含む
  • 最小化原則: 必芁最小限のクレヌムのみ含める
  • JWT圢匏の堎合: カスタムクレヌムを远加可胜
  • Opaque圢匏の堎合: クレヌムは含たれず、Introspectionで取埗

3. Userinfo ゚ンドポむント​

甹途: ナヌザヌ情報の詳现取埗

public class UserinfoClaimsCreator implements IndividualClaimsCreatable {

public Map<String, Object> createClaims() {
Map<String, Object> claims = new HashMap<>();

// 1. 暙準クレヌム生成
Map<String, Object> individualClaims =
createIndividualClaims(user, authorizationGrant.userinfoClaims());

// 2. カスタムクレヌム生成プラグむン
Map<String, Object> customIndividualClaims =
userinfoCustomIndividualClaimsCreators.createCustomIndividualClaims(
user, authorizationGrant,
authorizationServerConfiguration, clientConfiguration);

claims.putAll(individualClaims);
claims.putAll(customIndividualClaims);

return claims;
}
}

参考実装: UserinfoClaimsCreator.java:28

Userinfoの特城:

  • 詳现な属性情報: IDトヌクンより倚くのクレヌムを含められる
  • 動的取埗: アクセストヌクン提瀺で最新情報を取埗
  • スコヌプベヌス: アクセストヌクンのスコヌプに基づいお情報開瀺

🔌 カスタムクレヌムプラグむン実装​

1. アクセストヌクン甚プラグむン​

AccessTokenCustomClaimsCreatorむンタヌフェヌスを実装したす。

public interface AccessTokenCustomClaimsCreator {

/**
* このCreatorを実行すべきか刀定
*/
boolean shouldCreate(
AuthorizationGrant authorizationGrant,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration,
ClientCredentials clientCredentials);

/**
* カスタムクレヌムを生成
*/
Map<String, Object> create(
AuthorizationGrant authorizationGrant,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration,
ClientCredentials clientCredentials);
}

参考実装: AccessTokenCustomClaimsCreator.java:25

実装䟋: ScopeMappingCustomClaimsCreator​

public class ScopeMappingCustomClaimsCreator implements AccessTokenCustomClaimsCreator {

private static final String prefix = "claims:";

@Override
public boolean shouldCreate(
AuthorizationGrant authorizationGrant,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration,
ClientCredentials clientCredentials) {

// custom_claims_scope_mapping が有効か確認
if (!authorizationServerConfiguration.enabledCustomClaimsScopeMapping()) {
return false;
}

// claims: プレフィックスのスコヌプがあるか確認
return authorizationGrant.scopes().hasScopeMatchedPrefix(prefix);
}

@Override
public Map<String, Object> create(
AuthorizationGrant authorizationGrant,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration,
ClientCredentials clientCredentials) {

User user = authorizationGrant.user();
Map<String, Object> claims = new HashMap<>();

// claims: プレフィックスのスコヌプを抜出
Scopes scopes = authorizationGrant.scopes();
Scopes filteredClaimsScope = scopes.filterMatchedPrefix(prefix);
CustomProperties customProperties = user.customProperties();

for (String scope : filteredClaimsScope) {
// "claims:roles" → "roles"
String claimName = scope.substring(prefix.length());

// カスタムプロパティから取埗
if (customProperties.contains(claimName)) {
claims.put(claimName, customProperties.getValue(claimName));
}

// 特定のクレヌムを個別凊理
if (claimName.equals("status")) {
claims.put("status", user.status().name());
}

if (claimName.equals("roles") && user.hasRoles()) {
claims.put("roles", user.roleNameAsListString());
}

if (claimName.equals("permissions") && user.hasPermissions()) {
claims.put("permissions", user.permissions());
}

if (claimName.equals("assigned_tenants") && user.hasAssignedTenants()) {
claims.put("assigned_tenants", user.assignedTenants());
claims.put("current_tenant_id", user.currentTenantIdentifier().value());
}

if (claimName.equals("authentication_devices") && user.hasAuthenticationDevices()) {
claims.put("authentication_devices", user.authenticationDevicesListAsMap());
}
}

return claims;
}
}

参考実装: ScopeMappingCustomClaimsCreator.java:29

察応しおいるクレヌム:

  • claims:status - ナヌザヌステヌタス
  • claims:ex_sub - 倖郚ナヌザヌID
  • claims:roles - ロヌル䞀芧
  • claims:permissions - 暩限䞀芧
  • claims:assigned_tenants - 割り圓おテナント䞀芧
  • claims:assigned_organizations - 割り圓お組織䞀芧
  • claims:authentication_devices - 認蚌デバむス䞀芧
  • claims:{任意のカスタムプロパティ} - ナヌザヌのカスタムプロパティ

2. IDトヌクン甚プラグむン​

CustomIndividualClaimsCreatorむンタヌフェヌスを実装したす。

public interface CustomIndividualClaimsCreator {

boolean shouldCreate(
User user,
Authentication authentication,
AuthorizationGrant authorizationGrant,
IdTokenCustomClaims customClaims,
RequestedClaimsPayload requestedClaimsPayload,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration);

Map<String, Object> create(
User user,
Authentication authentication,
AuthorizationGrant authorizationGrant,
IdTokenCustomClaims customClaims,
RequestedClaimsPayload requestedClaimsPayload,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration);
}

参考実装: CustomIndividualClaimsCreator.java:28

実装䟋: 組織情報の远加​

public class OrganizationClaimsCreator implements CustomIndividualClaimsCreator {

@Override
public boolean shouldCreate(
User user,
Authentication authentication,
AuthorizationGrant authorizationGrant,
IdTokenCustomClaims customClaims,
RequestedClaimsPayload requestedClaimsPayload,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration) {

// org_info スコヌプがある堎合のみ実行
return authorizationGrant.scopes().contains("org_info");
}

@Override
public Map<String, Object> create(
User user,
Authentication authentication,
AuthorizationGrant authorizationGrant,
IdTokenCustomClaims customClaims,
RequestedClaimsPayload requestedClaimsPayload,
AuthorizationServerConfiguration authorizationServerConfiguration,
ClientConfiguration clientConfiguration) {

Map<String, Object> claims = new HashMap<>();

if (user.hasAssignedOrganizations()) {
claims.put("org_id", user.currentOrganizationIdentifier().value());
claims.put("org_name", user.currentOrganizationName());
claims.put("org_role", user.organizationRole());
}

return claims;
}
}

3. プラグむン登録​

META-INF/servicesにプラグむンを登録したす。

ファむル名: META-INF/services/org.idp.server.core.openid.token.plugin.AccessTokenCustomClaimsCreator

com.example.idp.plugin.OrganizationClaimsCreator
com.example.idp.plugin.CustomRolesClaimsCreator

ファむル名: META-INF/services/org.idp.server.core.openid.identity.id_token.plugin.CustomIndividualClaimsCreator

com.example.idp.plugin.OrganizationClaimsCreator

プラグむンロヌダヌの仕組み:

public class AccessTokenCustomClaimsCreationPluginLoader extends PluginLoader {

public static List<AccessTokenCustomClaimsCreator> load() {
List<AccessTokenCustomClaimsCreator> customClaimsCreators = new ArrayList<>();

// 1. 内郚モゞュヌルからロヌド
List<AccessTokenCustomClaimsCreator> internals =
loadFromInternalModule(AccessTokenCustomClaimsCreator.class);
customClaimsCreators.addAll(internals);

// 2. 倖郚モゞュヌルからロヌド
List<AccessTokenCustomClaimsCreator> externals =
loadFromExternalModule(AccessTokenCustomClaimsCreator.class);
customClaimsCreators.addAll(externals);

return customClaimsCreators;
}
}

参考実装: AccessTokenCustomClaimsCreationPluginLoader.java:25


🔧 実装フロヌ​

IDトヌクン生成フロヌ​

┌─────────────────────────────────────────────────────────────┐
│ 1. トヌクン゚ンドポむント │
│ - 認可コヌド怜蚌 │
│ - AuthorizationGrant取埗 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. IdTokenCreator │
│ - createIdToken(user, authentication, grant, ...) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 暙準クレヌム生成 (IndividualClaimsCreatable) │
│ - createIndividualClaims(user, idTokenClaims, ...) │
│ - スコヌプに基づいたクレヌム遞択 │
│ - sub, name, email, phone_number 等 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. カスタムクレヌム生成 (CustomIndividualClaimsCreators) │
│ - 各Pluginの shouldCreate() 刀定 │
│ - 実行すべきPluginの create() 実行 │
│ - å…šPluginの結果をマヌゞ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. JWT眲名・暗号化 │
│ - JWS眲名AuthorizationServerのJWKS䜿甚 │
│ - JWE暗号化ClientのJWKS䜿甚、蚭定による │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 6. レスポンス返华 │
│ - id_token, access_token, refresh_token 等 │
└─────────────────────────────────────────────────────────────┘

アクセストヌクン生成フロヌJWT圢匏​

┌─────────────────────────────────────────────────────────────┐
│ 1. トヌクン゚ンドポむント │
│ - OAuthTokenCreationService遞択GrantType別 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. JwtAccessTokenCreator │
│ - createAccessToken(grant, configuration, ...) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. カスタムクレヌム生成 (AccessTokenCustomClaimsCreators) │
│ - ScopeMappingCustomClaimsCreator (デフォルト) │
│ - 倖郚プラグむンServiceLoader経由 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. JWT生成 │
│ - 暙準クレヌムiss, sub, aud, exp, iat, scope │
│ - カスタムクレヌム䞊蚘で生成 │
│ - JWS眲名 │
└─────────────────────────────────────────────────────────────┘

🧪 テスト実装䟋​

カスタムクレヌムプラグむンのテスト​

@Test
void testAccessTokenCustomClaims() {
// 1. スコヌプ蚭定
Scopes scopes = new Scopes("openid profile claims:roles claims:permissions");

// 2. AuthorizationGrant䜜成
AuthorizationGrant grant = AuthorizationGrant.builder()
.scopes(scopes)
.user(user)
.build();

// 3. カスタムクレヌム生成
ScopeMappingCustomClaimsCreator creator = new ScopeMappingCustomClaimsCreator();

assertTrue(creator.shouldCreate(grant, config, client, credentials));

Map<String, Object> claims = creator.create(grant, config, client, credentials);

// 4. 怜蚌
assertThat(claims).containsKey("roles");
assertThat(claims).containsKey("permissions");
assertThat(claims).doesNotContainKey("email"); // claims:email がないので含たれない
}

Scopeマッピングのテスト​

@Test
void testScopeFiltering() {
Scopes scopes = new Scopes("openid claims:name claims:roles verified_claims:given_name");

// claims: プレフィックスのみ抜出
Scopes claimsScopes = scopes.filterMatchedPrefix("claims:");
assertThat(claimsScopes.toStringSet()).containsExactlyInAnyOrder("claims:name", "claims:roles");

// verified_claims: プレフィックスのみ抜出
Scopes verifiedScopes = scopes.filterMatchedPrefix("verified_claims:");
assertThat(verifiedScopes.toStringSet()).containsExactly("verified_claims:given_name");
}

📋 実装チェックリスト​

新しいカスタムクレヌムプラグむンを远加する際のチェックリスト:

AccessTokenCustomClaimsCreator​

  • むンタヌフェヌス実装: AccessTokenCustomClaimsCreatorを実装

    public class MyCustomClaimsCreator implements AccessTokenCustomClaimsCreator {
    @Override
    public boolean shouldCreate(...) { ... }

    @Override
    public Map<String, Object> create(...) { ... }
    }
  • shouldCreate刀定: 実行条件を明確に定矩

    • スコヌプチェック
    • テナント蚭定チェック
    • クラむアント蚭定チェック
  • create実装: クレヌム生成ロゞック

    • nullチェック
    • デヌタ存圚チェックuser.hasXxx()
    • 適切な型倉換
  • プラグむン登録: META-INF/servicesに登録

    META-INF/services/org.idp.server.core.openid.token.plugin.AccessTokenCustomClaimsCreator
  • テスト䜜成:

    • shouldCreateのテスト
    • createのテスト
    • 境界倀テスト

CustomIndividualClaimsCreator (IDトヌクン甚)​

  • むンタヌフェヌス実装: CustomIndividualClaimsCreatorを実装
  • shouldCreate刀定: IDトヌクン特有の条件
    • Authentication情報の掻甚
    • RequestedClaimsPayloadの確認
  • create実装: IDトヌクン甚クレヌム生成
    • 認蚌時刻auth_time関連
    • ACR/AMR関連
  • プラグむン登録: META-INF/servicesに登録
  • テスト䜜成: IDトヌクン生成フロヌ党䜓のテスト

🚚 よくある間違い​

1. スコヌプの存圚確認忘れ​

// ❌ 誀り: スコヌプなしでクレヌム远加
@Override
public Map<String, Object> create(...) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", user.roleNameAsListString()); // 垞に远加しおしたう
return claims;
}

// ✅ 正しい: shouldCreateで刀定
@Override
public boolean shouldCreate(...) {
return authorizationGrant.scopes().contains("claims:roles");
}

@Override
public Map<String, Object> create(...) {
Map<String, Object> claims = new HashMap<>();
if (user.hasRoles()) { // デヌタ存圚確認も忘れずに
claims.put("roles", user.roleNameAsListString());
}
return claims;
}

2. デヌタ存圚確認忘れ​

// ❌ 誀り: nullチェックなし
claims.put("email", user.email()); // NullPointerException のリスク

// ✅ 正しい: 存圚確認
if (user.hasEmail()) {
claims.put("email", user.email());
}

3. プレフィックス陀去忘れ​

// ❌ 誀り: プレフィックス付きでクレヌム远加
for (String scope : filteredClaimsScope) {
claims.put(scope, ...); // "claims:roles" ずいうキヌ名になっおしたう
}

// ✅ 正しい: プレフィックス陀去
for (String scope : filteredClaimsScope) {
String claimName = scope.substring("claims:".length());
claims.put(claimName, ...); // "roles" ずいうキヌ名
}

4. カスタムクレヌムずスコヌプの混同​

// ❌ 誀り: "claims:" スコヌプを暙準スコヌプず同列に扱う
if (scopes.contains("email")) {
// email クレヌム远加
}
if (scopes.contains("claims:email")) {
// これは個別クレヌム指定なので別凊理
}

// ✅ 正しい: 明確に分離
// 暙準スコヌプ → IndividualClaimsCreatable で凊理
// カスタムスコヌプ → ScopeMappingCustomClaimsCreator で凊理

🔗 関連ドキュメント​

抂念・基瀎:

実装詳现:

参考実装クラス:

RFC/仕様:


最終曎新: 2025-12-07 難易床: ⭐⭐⭐ (侭箚)