Add displayName to auth response and configurable display name claim for OIDC
- Add displayName field to AuthTokenResponse so the UI shows human-readable names instead of internal JWT subjects (e.g. user:oidc:<hash>) - Add displayNameClaim to OIDC config (default: "name") allowing admins to configure which ID token claim contains the user's display name - Support dot-separated claim paths (e.g. profile.display_name) like rolesClaim - Add admin UI field for Display Name Claim on the OIDC config page - ClickHouse migration: ALTER TABLE adds display_name_claim column Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,7 +130,9 @@ public class OidcAuthController {
|
||||
String accessToken = jwtService.createAccessToken(userId, "user", roles);
|
||||
String refreshToken = jwtService.createRefreshToken(userId, "user", roles);
|
||||
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken));
|
||||
String displayName = oidcUser.name() != null && !oidcUser.name().isBlank()
|
||||
? oidcUser.name() : oidcUser.email();
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken, displayName));
|
||||
} catch (ResponseStatusException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -95,10 +95,7 @@ public class OidcTokenExchanger {
|
||||
|
||||
String subject = claims.getSubject();
|
||||
String email = claims.getStringClaim("email");
|
||||
String name = claims.getStringClaim("name");
|
||||
if (name == null) {
|
||||
name = claims.getStringClaim("preferred_username");
|
||||
}
|
||||
String name = extractStringClaim(claims, config.displayNameClaim());
|
||||
|
||||
List<String> roles = extractRoles(claims, config.rolesClaim());
|
||||
|
||||
@@ -147,6 +144,24 @@ public class OidcTokenExchanger {
|
||||
.orElseThrow(() -> new IllegalStateException("OIDC is not configured or disabled"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String extractStringClaim(JWTClaimsSet claims, String claimPath) {
|
||||
if (claimPath == null || claimPath.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
String[] parts = claimPath.split("\\.");
|
||||
Object current = claims.getClaim(parts[0]);
|
||||
for (int i = 1; i < parts.length && current instanceof Map; i++) {
|
||||
current = ((Map<String, Object>) current).get(parts[i]);
|
||||
}
|
||||
return current instanceof String ? (String) current : null;
|
||||
} catch (Exception e) {
|
||||
log.debug("Could not extract string from claim path '{}': {}", claimPath, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> extractRoles(JWTClaimsSet claims, String claimPath) {
|
||||
try {
|
||||
|
||||
@@ -74,7 +74,8 @@ public class SecurityBeanConfig {
|
||||
envOidc.getClientSecret() != null ? envOidc.getClientSecret() : "",
|
||||
envOidc.getRolesClaim(),
|
||||
envOidc.getDefaultRoles(),
|
||||
true
|
||||
true,
|
||||
"name"
|
||||
);
|
||||
configRepository.save(config);
|
||||
log.info("OIDC config seeded from environment variables: issuer={}", envOidc.getIssuerUri());
|
||||
|
||||
@@ -85,7 +85,7 @@ public class UiAuthController {
|
||||
String refreshToken = jwtService.createRefreshToken(subject, "user", roles);
|
||||
|
||||
log.info("UI user logged in: {}", request.username());
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken));
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken, request.username()));
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
@@ -105,7 +105,10 @@ public class UiAuthController {
|
||||
String accessToken = jwtService.createAccessToken(result.subject(), "user", roles);
|
||||
String refreshToken = jwtService.createRefreshToken(result.subject(), "user", roles);
|
||||
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken));
|
||||
String displayName = userRepository.findById(result.subject())
|
||||
.map(UserInfo::displayName)
|
||||
.orElse(result.subject());
|
||||
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken, displayName));
|
||||
} catch (ResponseStatusException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
|
||||
Reference in New Issue
Block a user