feat(auth): switch CustomUserDetailsService to email-based lookup
loadUserByUsername now calls findByEmail and returns email as the Spring Security principal name. Tests updated to assert email identity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,24 +29,22 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
private final AppUserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
AppUser appUser = userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User nicht gefunden: " + username));
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
AppUser appUser = userRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User nicht gefunden: " + email));
|
||||
|
||||
// Collect all permissions from all groups; warn about any that don't match a known Permission enum value
|
||||
var authorities = appUser.getGroups().stream()
|
||||
.flatMap(group -> group.getPermissions().stream())
|
||||
.peek(p -> {
|
||||
if (!KNOWN_PERMISSIONS.contains(p)) {
|
||||
log.warn("Unknown permission '{}' found in database for user '{}' — it will be granted but never matched by @RequirePermission", p, appUser.getUsername());
|
||||
log.warn("Unknown permission '{}' found in database for user '{}' — it will be granted but never matched by @RequirePermission", p, appUser.getEmail());
|
||||
}
|
||||
})
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Rückgabe des Standard Spring Security User Objekts
|
||||
return new User(
|
||||
appUser.getUsername(),
|
||||
appUser.getEmail(),
|
||||
appUser.getPassword(),
|
||||
appUser.isEnabled(),
|
||||
true, true, true,
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.raddatz.familienarchiv.model.AppUser;
|
||||
import org.raddatz.familienarchiv.model.UserGroup;
|
||||
import org.raddatz.familienarchiv.repository.AppUserRepository;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
@@ -29,40 +28,40 @@ class CustomUserDetailsServiceTest {
|
||||
// ─── loadUserByUsername — not found ──────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void loadUserByUsername_throwsUsernameNotFoundException_whenUserNotFound() {
|
||||
when(userRepository.findByUsername("ghost")).thenReturn(Optional.empty());
|
||||
void loadUserByEmail_throwsUsernameNotFoundException_whenUserNotFound() {
|
||||
when(userRepository.findByEmail("ghost@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> service.loadUserByUsername("ghost"))
|
||||
assertThatThrownBy(() -> service.loadUserByUsername("ghost@example.com"))
|
||||
.isInstanceOf(UsernameNotFoundException.class)
|
||||
.hasMessageContaining("ghost");
|
||||
.hasMessageContaining("ghost@example.com");
|
||||
}
|
||||
|
||||
// ─── loadUserByUsername — happy path ─────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void loadUserByUsername_returnsUserDetails_withMappedAuthorities() {
|
||||
void loadUserByEmail_returnsUserDetails_withEmailAsPrincipal() {
|
||||
UserGroup group = UserGroup.builder().id(UUID.randomUUID()).name("Admins")
|
||||
.permissions(Set.of("READ_ALL", "WRITE_ALL")).build();
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID())
|
||||
.username("admin").password("hashed").enabled(true)
|
||||
.email("admin@example.com").password("hashed").enabled(true)
|
||||
.groups(Set.of(group)).build();
|
||||
when(userRepository.findByUsername("admin")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByEmail("admin@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
UserDetails details = service.loadUserByUsername("admin");
|
||||
UserDetails details = service.loadUserByUsername("admin@example.com");
|
||||
|
||||
assertThat(details.getUsername()).isEqualTo("admin");
|
||||
assertThat(details.getUsername()).isEqualTo("admin@example.com");
|
||||
assertThat(details.getAuthorities()).extracting("authority")
|
||||
.contains("READ_ALL", "WRITE_ALL");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadUserByUsername_returnsEmptyAuthorities_whenUserHasNoGroups() {
|
||||
void loadUserByEmail_returnsEmptyAuthorities_whenUserHasNoGroups() {
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID())
|
||||
.username("viewer").password("hashed").enabled(true)
|
||||
.email("viewer@example.com").password("hashed").enabled(true)
|
||||
.groups(Set.of()).build();
|
||||
when(userRepository.findByUsername("viewer")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByEmail("viewer@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
UserDetails details = service.loadUserByUsername("viewer");
|
||||
UserDetails details = service.loadUserByUsername("viewer@example.com");
|
||||
|
||||
assertThat(details.getAuthorities()).isEmpty();
|
||||
}
|
||||
@@ -70,16 +69,15 @@ class CustomUserDetailsServiceTest {
|
||||
// ─── loadUserByUsername — unknown permission ──────────────────────────────
|
||||
|
||||
@Test
|
||||
void loadUserByUsername_grantsUnknownPermission_butLogsWarning() {
|
||||
// Unknown permissions should still be granted (logged as warning, not silently dropped)
|
||||
void loadUserByEmail_grantsUnknownPermission_butLogsWarning() {
|
||||
UserGroup group = UserGroup.builder().id(UUID.randomUUID()).name("CustomGroup")
|
||||
.permissions(Set.of("UNKNOWN_CUSTOM_PERM")).build();
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID())
|
||||
.username("custom").password("hashed").enabled(true)
|
||||
.email("custom@example.com").password("hashed").enabled(true)
|
||||
.groups(Set.of(group)).build();
|
||||
when(userRepository.findByUsername("custom")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByEmail("custom@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
UserDetails details = service.loadUserByUsername("custom");
|
||||
UserDetails details = service.loadUserByUsername("custom@example.com");
|
||||
|
||||
assertThat(details.getAuthorities()).extracting("authority")
|
||||
.contains("UNKNOWN_CUSTOM_PERM");
|
||||
@@ -88,13 +86,13 @@ class CustomUserDetailsServiceTest {
|
||||
// ─── loadUserByUsername — disabled user ───────────────────────────────────
|
||||
|
||||
@Test
|
||||
void loadUserByUsername_returnsDisabledUser_whenUserIsDisabled() {
|
||||
void loadUserByEmail_returnsDisabledUser_whenUserIsDisabled() {
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID())
|
||||
.username("disabled").password("hashed").enabled(false)
|
||||
.email("disabled@example.com").password("hashed").enabled(false)
|
||||
.groups(Set.of()).build();
|
||||
when(userRepository.findByUsername("disabled")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByEmail("disabled@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
UserDetails details = service.loadUserByUsername("disabled");
|
||||
UserDetails details = service.loadUserByUsername("disabled@example.com");
|
||||
|
||||
assertThat(details.isEnabled()).isFalse();
|
||||
}
|
||||
@@ -102,17 +100,17 @@ class CustomUserDetailsServiceTest {
|
||||
// ─── loadUserByUsername — multi-group permission merge ────────────────────
|
||||
|
||||
@Test
|
||||
void loadUserByUsername_mergesPermissionsFromMultipleGroups() {
|
||||
void loadUserByEmail_mergesPermissionsFromMultipleGroups() {
|
||||
UserGroup g1 = UserGroup.builder().id(UUID.randomUUID()).name("Readers")
|
||||
.permissions(Set.of("READ_ALL")).build();
|
||||
UserGroup g2 = UserGroup.builder().id(UUID.randomUUID()).name("Writers")
|
||||
.permissions(Set.of("WRITE_ALL")).build();
|
||||
AppUser user = AppUser.builder().id(UUID.randomUUID())
|
||||
.username("multi").password("hashed").enabled(true)
|
||||
.email("multi@example.com").password("hashed").enabled(true)
|
||||
.groups(Set.of(g1, g2)).build();
|
||||
when(userRepository.findByUsername("multi")).thenReturn(Optional.of(user));
|
||||
when(userRepository.findByEmail("multi@example.com")).thenReturn(Optional.of(user));
|
||||
|
||||
UserDetails details = service.loadUserByUsername("multi");
|
||||
UserDetails details = service.loadUserByUsername("multi@example.com");
|
||||
|
||||
assertThat(details.getAuthorities()).extracting("authority")
|
||||
.containsExactlyInAnyOrder("READ_ALL", "WRITE_ALL");
|
||||
|
||||
Reference in New Issue
Block a user