diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OidcConfigAdminController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OidcConfigAdminController.java index a7d500c6..053fd36c 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OidcConfigAdminController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/OidcConfigAdminController.java @@ -101,7 +101,8 @@ public class OidcConfigAdminController { request.rolesClaim() != null ? request.rolesClaim() : "roles", request.defaultRoles() != null ? request.defaultRoles() : List.of("VIEWER"), request.autoSignup(), - request.displayNameClaim() != null ? request.displayNameClaim() : "name" + request.displayNameClaim() != null ? request.displayNameClaim() : "name", + request.userIdClaim() != null ? request.userIdClaim() : "sub" ); configRepository.save(config); diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigRequest.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigRequest.java index 09ffebcf..ee8ecb91 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigRequest.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigRequest.java @@ -13,5 +13,6 @@ public record OidcAdminConfigRequest( String rolesClaim, List defaultRoles, boolean autoSignup, - String displayNameClaim + String displayNameClaim, + String userIdClaim ) {} diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigResponse.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigResponse.java index eb5d1556..bf15b579 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigResponse.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/dto/OidcAdminConfigResponse.java @@ -16,17 +16,19 @@ public record OidcAdminConfigResponse( String rolesClaim, List defaultRoles, boolean autoSignup, - String displayNameClaim + String displayNameClaim, + String userIdClaim ) { 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) { return new OidcAdminConfigResponse( true, config.enabled(), config.issuerUri(), config.clientId(), !config.clientSecret().isBlank(), config.rolesClaim(), - config.defaultRoles(), config.autoSignup(), config.displayNameClaim() + config.defaultRoles(), config.autoSignup(), config.displayNameClaim(), + config.userIdClaim() ); } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcTokenExchanger.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcTokenExchanger.java index c4e1c835..ee561419 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcTokenExchanger.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/OidcTokenExchanger.java @@ -104,13 +104,20 @@ public class OidcTokenExchanger { 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 name = extractStringClaim(claims, config.displayNameClaim()); List 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); } diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/OidcConfig.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/OidcConfig.java index cbcc2888..5a414bed 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/OidcConfig.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/OidcConfig.java @@ -13,6 +13,7 @@ import java.util.List; * @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 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( boolean enabled, @@ -22,9 +23,10 @@ public record OidcConfig( String rolesClaim, List defaultRoles, boolean autoSignup, - String displayNameClaim + String displayNameClaim, + String userIdClaim ) { 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"); } }