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ã§ååŸ
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ããŒã¯ã³ããå€ãã®ã¯ã¬ãŒã ãå«ãããã
- åçååŸ: ã¢ã¯ã»ã¹ããŒã¯ã³æç€ºã§ææ°æ å ±ãååŸ
- ã¹ã³ãŒãããŒã¹: ã¢ã¯ã»ã¹ããŒã¯ã³ã®ã¹ã³ãŒãã«åºã¥ããŠæ å ±é瀺