Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
5b4abb6
Merge remote-tracking branch 'origin/master'
psevestre Oct 28, 2025
68e7399
[BAEL-9510] WIP
psevestre Nov 4, 2025
619960f
Merge remote-tracking branch 'origin/master'
psevestre Nov 4, 2025
56c5761
[BAEL-9510] WIP
psevestre Nov 6, 2025
d1a7236
Merge remote-tracking branch 'origin/master'
psevestre Nov 6, 2025
a1249ff
WIP
psevestre Nov 11, 2025
e530173
[BAEL-9515] WIP - LiveTest
psevestre Nov 12, 2025
d081722
Merge remote-tracking branch 'origin/master'
psevestre Nov 12, 2025
e5242f4
[BAEL-9515] Integration Test for Happy Path
psevestre Nov 17, 2025
ec32c43
[BAEL-9515] Integration Test for Happy Path
psevestre Nov 17, 2025
22f39d1
Merge remote-tracking branch 'origin/master'
psevestre Nov 17, 2025
9efb738
Merge remote-tracking branch 'origin/master'
psevestre Nov 18, 2025
66c333d
Merge remote-tracking branch 'origin/master'
psevestre Nov 18, 2025
adb23c3
[BAEL-9510] Code cleanup
psevestre Nov 18, 2025
86bfdba
[BAEl-9510] WIP: Code cleanup
psevestre Nov 19, 2025
14e6608
Merge remote-tracking branch 'origin/master'
psevestre Nov 24, 2025
e2347e6
[BAEL-9510] code cleanup and test improvements
psevestre Nov 24, 2025
91f85d2
Merge remote-tracking branch 'origin/master'
psevestre Jan 7, 2026
f4b98cc
Merge remote-tracking branch 'origin/master'
psevestre Jan 15, 2026
b7581dd
[BAEL-8408] WIP
psevestre Jan 16, 2026
2099771
Merge remote-tracking branch 'origin/master'
psevestre Jan 16, 2026
bdef830
[BAEL-8408] Multitenant Spring Auth Server
psevestre Jan 18, 2026
062090b
Merge remote-tracking branch 'origin/master'
psevestre Jan 18, 2026
a90fe06
[BAEL-8408] Fix SB4 Tests
psevestre Jan 18, 2026
1fbcc0f
[BAEL-8408] Remove log files from commit
psevestre Jan 18, 2026
8a32ca3
[BAEL-8408] Code cleanup
psevestre Jan 22, 2026
b5ac50d
Merge remote-tracking branch 'origin/master'
psevestre Jan 22, 2026
f0042cb
[BAEL-8408] Fix dependencies
psevestre Jan 22, 2026
4784ded
[BAEL-8408] throw exception for unknown issuer
psevestre Jan 23, 2026
563e079
Merge remote-tracking branch 'origin/master'
psevestre Jan 23, 2026
769786e
Merge remote-tracking branch 'origin/master'
psevestre Apr 4, 2026
1acf4ce
[BAEL-9649] WIP
psevestre May 12, 2026
d41f950
Merge remote-tracking branch 'origin/master'
psevestre May 12, 2026
a6a6bcc
WIP: Initial project structure
psevestre May 12, 2026
e063c3b
[BAEL-9649] WIP
psevestre May 22, 2026
f1972f1
Merge remote-tracking branch 'origin/master'
psevestre May 22, 2026
93e7a1b
[BAEL-9649] Dynamic scopes
psevestre May 28, 2026
8de30f9
Merge remote-tracking branch 'origin/master'
psevestre May 28, 2026
f85b97f
[BAEL-9649] UnitTest
psevestre May 28, 2026
79add9d
Merge remote-tracking branch 'origin/master'
psevestre May 29, 2026
d86defa
Code cleanup
psevestre May 31, 2026
d0ed23f
Merge remote-tracking branch 'origin/master'
psevestre May 31, 2026
82af2a4
[BAEL-9649] Code cleanup
psevestre Jun 1, 2026
72da7aa
Merge remote-tracking branch 'origin/master'
psevestre Aug 30, 2026
ca6a6a3
[BAEL-7339] Userinfo custom claims
psevestre Sep 3, 2026
d85849e
Merge remote-tracking branch 'origin/master'
psevestre Sep 3, 2026
e036d9e
[BAEL-7332] Extra test case
psevestre Sep 7, 2026
ce34367
Merge remote-tracking branch 'origin/master'
psevestre Sep 7, 2026
e40137f
[BAEL-7332] Code cleanup
psevestre Sep 7, 2026
00f2e47
Merge remote-tracking branch 'origin/master'
psevestre Oct 6, 2026
d9728d3
[BAEL-7339] Improve example with proper scope filtering
psevestre Oct 6, 2026
da22df2
Merge remote-tracking branch 'origin/master'
psevestre Oct 7, 2026
d1e581c
[BAEL-7339] Test code cleanup
psevestre Oct 7, 2026
a4ac1d3
[BAEL-7339] Code cleanup
psevestre Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.baeldung.auth.server.customclaims;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(com.baeldung.auth.server.customclaims.config.AuthServerConfiguration.class)
public class CustomClaimsAuthServerApplication {

public static void main(String[] args) {
SpringApplication.run(CustomClaimsAuthServerApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.baeldung.auth.server.customclaims.components;

import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;

import java.util.function.Function;

public class UserInfoMapper implements Function<OidcUserInfoAuthenticationContext, OidcUserInfo> {

private final UserInfoService userInfoService;

public UserInfoMapper(UserInfoService userInfoService) {
this.userInfoService = userInfoService;
}

@Override
public OidcUserInfo apply(OidcUserInfoAuthenticationContext context) {

var subject = context.getAuthentication().getName();
var scopes = context.getAuthorization().getAuthorizedScopes();
var userInfo = userInfoService.getUserInfoByUsername(subject);

// Handle trivial cases first
if ( userInfo.isEmpty() || scopes.isEmpty() || (scopes.size() == 1 && scopes.contains("openid")) ) {
return OidcUserInfo.builder()
.subject(subject)
.build();
}

// Add claims based on the authorized scopes
var user = userInfo.get();
var b = OidcUserInfo.builder()
.subject(subject);

if ( scopes.contains("email") ) {
b.email(user.email())
.emailVerified(user.emailVerified());
}

if( scopes.contains("profile") ) {
b.name(user.name())
.givenName(user.givenName())
.familyName(user.familyName())
.locale(user.locale().toLanguageTag())
.gender(user.gender())
.birthdate(user.birthdate().toString())
.zoneinfo(user.zoneId().toString())
.preferredUsername(user.username())
.updatedAt(user.updatedAt().toString());
}

// Add custom claims only if authorized. Here, we'll use the "account" as an example
if ( scopes.contains("account") ) {
b.claim("account_id", user.accountId().toString())
.claim("created_at", user.createdAt().toString())
.claim("account_expires_at", user.accountExpiresAt().toString());
}

// Build the final result
return b.build();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.baeldung.auth.server.customclaims.components;

import org.slf4j.Logger;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;

@Service
public class UserInfoService {

static final Map<String, UserInfo> users = Map.of(
"user", new UserInfo(
"user",
"Alice Smith",
"Alice",
"Smith",
"alice.smith@example.com",
true,
Locale.forLanguageTag("en-US"),
"female",
LocalDate.of(1990, 1, 1),
ZoneId.of("America/New_York"),
UUID.randomUUID(),
Instant.now(),
Instant.now(),
Instant.now().plus(60, java.time.temporal.ChronoUnit.DAYS)
)
);

public Optional<UserInfo> getUserInfoByUsername(String username) {
return Optional.ofNullable(users.get(username));
}

public record UserInfo(
String username,
String name,
String givenName,
String familyName,
String email,
boolean emailVerified,
Locale locale,
String gender,
LocalDate birthdate,
ZoneId zoneId,
UUID accountId,
Instant createdAt,
Instant updatedAt,
Instant accountExpiresAt
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.baeldung.auth.server.customclaims.config;

import com.baeldung.auth.server.customclaims.components.UserInfoMapper;
import com.baeldung.auth.server.customclaims.components.UserInfoService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;

import java.util.List;
import java.util.Set;
import java.util.function.Function;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
public class AuthServerConfiguration {
private static final Logger log = org.slf4j.LoggerFactory.getLogger(AuthServerConfiguration.class);

private final UserInfoService userInfoService;

@Value("${customuserinfo.enabled:true}")
private boolean enableCustomUserInfo = true;

public AuthServerConfiguration(UserInfoService userInfoService) {
this.userInfoService = userInfoService;
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, UserInfoMapper userInfoMapper) {

// @formatter:off
return http.oauth2AuthorizationServer(sas -> {
http
.securityMatcher(sas.getEndpointsMatcher())
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.exceptionHandling(exceptions ->
exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"), createRequestMatcher()));

if ( enableCustomUserInfo ) {
sas.oidc(oidc -> oidc
.userInfoEndpoint(userInfo -> userInfo
.userInfoMapper(userInfoMapper)));
}
else {
sas.oidc(withDefaults());
}
}).build();
// @formatter:on
}

@Bean
UserInfoMapper userInfoMapper() {
return new UserInfoMapper(userInfoService);
}

@Bean
@Order(SecurityFilterProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) {
// @formatter:off
http
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}

private static RequestMatcher createRequestMatcher() {
MediaTypeRequestMatcher requestMatcher = new MediaTypeRequestMatcher(MediaType.TEXT_HTML);
requestMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
return requestMatcher;
}

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
spring:
security:
user:
name: user
password: "{noop}password"
oauth2:
authorizationserver:
client:
client1:
require-proof-key: false
require-authorization-consent: false
registration:
client-name: "Client 1 - Issuer 1"
client-id: client1
client-secret: "{noop}secret1"
client-authentication-methods:
- client_secret_basic
redirect-uris:
- http://localhost:9090/login/oauth2/code/issuer1client1
authorization-grant-types:
- authorization_code
- refresh_token
scopes:
- openid
- email
- profile
- account

Loading