feat: add configurable userIdClaim for OIDC user identification
The OIDC user login ID is now configurable via the admin OIDC setup dialog (userIdClaim field). Supports dot-separated claim paths (e.g. 'email', 'preferred_username', 'custom.user_id'). Defaults to 'sub' for backwards compatibility. Throws if the configured claim is missing from the id_token. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,7 +101,8 @@ public class OidcConfigAdminController {
|
|||||||
request.rolesClaim() != null ? request.rolesClaim() : "roles",
|
request.rolesClaim() != null ? request.rolesClaim() : "roles",
|
||||||
request.defaultRoles() != null ? request.defaultRoles() : List.of("VIEWER"),
|
request.defaultRoles() != null ? request.defaultRoles() : List.of("VIEWER"),
|
||||||
request.autoSignup(),
|
request.autoSignup(),
|
||||||
request.displayNameClaim() != null ? request.displayNameClaim() : "name"
|
request.displayNameClaim() != null ? request.displayNameClaim() : "name",
|
||||||
|
request.userIdClaim() != null ? request.userIdClaim() : "sub"
|
||||||
);
|
);
|
||||||
|
|
||||||
configRepository.save(config);
|
configRepository.save(config);
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ public record OidcAdminConfigRequest(
|
|||||||
String rolesClaim,
|
String rolesClaim,
|
||||||
List<String> defaultRoles,
|
List<String> defaultRoles,
|
||||||
boolean autoSignup,
|
boolean autoSignup,
|
||||||
String displayNameClaim
|
String displayNameClaim,
|
||||||
|
String userIdClaim
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -16,17 +16,19 @@ public record OidcAdminConfigResponse(
|
|||||||
String rolesClaim,
|
String rolesClaim,
|
||||||
List<String> defaultRoles,
|
List<String> defaultRoles,
|
||||||
boolean autoSignup,
|
boolean autoSignup,
|
||||||
String displayNameClaim
|
String displayNameClaim,
|
||||||
|
String userIdClaim
|
||||||
) {
|
) {
|
||||||
public static OidcAdminConfigResponse unconfigured() {
|
public static OidcAdminConfigResponse unconfigured() {
|
||||||
return new OidcAdminConfigResponse(false, false, null, null, false, null, null, false, null);
|
return new OidcAdminConfigResponse(false, false, null, null, false, null, null, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OidcAdminConfigResponse from(OidcConfig config) {
|
public static OidcAdminConfigResponse from(OidcConfig config) {
|
||||||
return new OidcAdminConfigResponse(
|
return new OidcAdminConfigResponse(
|
||||||
true, config.enabled(), config.issuerUri(), config.clientId(),
|
true, config.enabled(), config.issuerUri(), config.clientId(),
|
||||||
!config.clientSecret().isBlank(), config.rolesClaim(),
|
!config.clientSecret().isBlank(), config.rolesClaim(),
|
||||||
config.defaultRoles(), config.autoSignup(), config.displayNameClaim()
|
config.defaultRoles(), config.autoSignup(), config.displayNameClaim(),
|
||||||
|
config.userIdClaim()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,13 +104,20 @@ public class OidcTokenExchanger {
|
|||||||
|
|
||||||
JWTClaimsSet claims = getJwtProcessor(config.issuerUri()).process(idTokenStr, null);
|
JWTClaimsSet claims = getJwtProcessor(config.issuerUri()).process(idTokenStr, null);
|
||||||
|
|
||||||
String subject = claims.getSubject();
|
String userIdClaim = config.userIdClaim() != null && !config.userIdClaim().isBlank()
|
||||||
|
? config.userIdClaim() : "sub";
|
||||||
|
String subject = "sub".equals(userIdClaim)
|
||||||
|
? claims.getSubject()
|
||||||
|
: extractStringClaim(claims, userIdClaim);
|
||||||
|
if (subject == null || subject.isBlank()) {
|
||||||
|
throw new IllegalStateException("OIDC id_token missing user ID claim: " + userIdClaim);
|
||||||
|
}
|
||||||
String email = claims.getStringClaim("email");
|
String email = claims.getStringClaim("email");
|
||||||
String name = extractStringClaim(claims, config.displayNameClaim());
|
String name = extractStringClaim(claims, config.displayNameClaim());
|
||||||
|
|
||||||
List<String> roles = extractRoles(claims, config.rolesClaim());
|
List<String> roles = extractRoles(claims, config.rolesClaim());
|
||||||
|
|
||||||
log.info("OIDC user authenticated: sub={}, email={}", subject, email);
|
log.info("OIDC user authenticated: id={}, email={}", subject, email);
|
||||||
return new OidcUserInfo(subject, email != null ? email : "", name != null ? name : "", roles, idTokenStr);
|
return new OidcUserInfo(subject, email != null ? email : "", name != null ? name : "", roles, idTokenStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import java.util.List;
|
|||||||
* @param defaultRoles fallback roles for new users with no OIDC role claim
|
* @param defaultRoles fallback roles for new users with no OIDC role claim
|
||||||
* @param autoSignup whether new OIDC users are automatically created on first login
|
* @param autoSignup whether new OIDC users are automatically created on first login
|
||||||
* @param displayNameClaim dot-separated path to display name in the id_token (e.g. {@code name}, {@code preferred_username})
|
* @param displayNameClaim dot-separated path to display name in the id_token (e.g. {@code name}, {@code preferred_username})
|
||||||
|
* @param userIdClaim dot-separated path to user identifier in the id_token (default {@code sub}); e.g. {@code email}, {@code preferred_username}
|
||||||
*/
|
*/
|
||||||
public record OidcConfig(
|
public record OidcConfig(
|
||||||
boolean enabled,
|
boolean enabled,
|
||||||
@@ -22,9 +23,10 @@ public record OidcConfig(
|
|||||||
String rolesClaim,
|
String rolesClaim,
|
||||||
List<String> defaultRoles,
|
List<String> defaultRoles,
|
||||||
boolean autoSignup,
|
boolean autoSignup,
|
||||||
String displayNameClaim
|
String displayNameClaim,
|
||||||
|
String userIdClaim
|
||||||
) {
|
) {
|
||||||
public static OidcConfig disabled() {
|
public static OidcConfig disabled() {
|
||||||
return new OidcConfig(false, "", "", "", "roles", List.of("VIEWER"), true, "name");
|
return new OidcConfig(false, "", "", "", "roles", List.of("VIEWER"), true, "name", "sub");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user