Scope & Claims
ð ãã®ããã¥ã¡ã³ãã®äœçœ®ã¥ãâ
察象èªè : OAuth 2.0/OIDC ã®ã¹ã³ãŒãã»ã¯ã¬ãŒã 管çã®å®è£ 詳现ãçè§£ãããéçºè
ãã®ããã¥ã¡ã³ãã§åŠã¹ãããš:
- ã¹ã³ãŒã管çã®å®è£ ãã¿ãŒã³
- IDããŒã¯ã³ã»ã¢ã¯ã»ã¹ããŒã¯ã³ã»Userinfoã§ã®ã¯ã¬ãŒã çæ
- ã«ã¹ã¿ã ã¯ã¬ãŒã ãã©ã°ã€ã³ã®å®è£ æ¹æ³
claims:/verified_claims:ãã¬ãã£ãã¯ã¹ã®ä»çµã¿- æšæºã¯ã¬ãŒã ã®ã¹ã³ãŒããããã³ã°
åæç¥è:
- basic-12: OpenID Connect詳解ã®çè§£
- concept-09: ã«ã¹ã¿ã ã¯ã¬ãŒã ã®çè§£
- impl-12: Pluginå®è£ ã¬ã€ãã®çè§£
ðïž ã¹ã³ãŒããšã¯ã¬ãŒã ã®é¢ä¿â
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 |
|---|---|
openid | sub |
profile | name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, updated_at |
email | email, email_verified |
phone | phone_number, phone_number_verified |
address | address |
å®è£ : 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- ãŠãŒã¶ãŒIDaud- ã¯ã©ã€ã¢ã³ãIDexp- æå¹æé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ã§ååŸ
verified_claimsã®åºåæ§é : 身å ç¢ºèªæžã¿ã¯ã¬ãŒã 㯠OIDC4IDA æºæ ã®ãã¹ãæ§é ïŒverification+claimsïŒã§åºåããããAccess Token / UserInfo ã®æ§é 仿§ãšãéå»ã®ãã©ããæ§é ããã®ç§»è¡æé 㯠verified_claims åºåæ§é ã®å€æŽïŒå©çšè 察å¿ïŒ ãåç §ã
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- å€éšãŠãŒã¶ãŒIDclaims:roles- ããŒã«äžèЧclaims:permissions- æš©éäžèЧclaims:assigned_tenants- å²ãåœãŠããã³ãäžèЧclaims:assigned_organizations- å²ãåœãŠçµç¹äžèЧclaims:authentication_devices- èªèšŒããã€ã¹äžèЧclaims:{ä»»æã®ã«ã¹ã¿ã ããããã£}- ãŠãŒã¶ãŒã®ã«ã¹ã¿ã ããããã£