Woche 6, Tag 2 — Users, Passwords und Authentication Flow
Ziel
Heute verstehst du Username/Password-Authentifizierung in Spring Security.
Die Kernfragen:
- Was ist ein Benutzer in Spring Security?
- Was ist
UserDetails? - Was ist
UserDetailsService? - Was ist
AuthenticationManager? - Was ist
AuthenticationProvider? - Was ist
DaoAuthenticationProvider? - Was ist
PasswordEncoder? - Warum müssen Passwörter gehasht werden?
- Wie funktioniert Datenbank-Login?
- Wie funktionieren Rollen und Berechtigungen?
- Wie erstellst du einen eigenen
UserDetailsService? - Was sind typische Prüfungsfallen?
1. Kurz-Wiederholung aus Woche 6, Tag 1
An Tag 1 hast du gelernt:
- Spring Security schützt Requests, bevor sie den Controller erreichen.
- Authentifizierung bedeutet, die Identität zu beweisen.
- Autorisierung bedeutet, Berechtigungen zu prüfen.
- 401 bedeutet: nicht authentifiziert.
- 403 bedeutet: authentifiziert, aber nicht erlaubt.
- Spring Security ist filter-chain-basiert.
SecurityFilterChaindefiniert Security-Regeln.HttpSecuritybaut dieSecurityFilterChain.SecurityContextHolderspeichert den aktuellen Security-Kontext.Authenticationrepräsentiert den aktuell authentifizierten Benutzer.PasswordEncoderspeichert und prüft Passwort-Hashes.- Speichere keine Klartext-Passwörter.
Merksatz:
Authentication = who are you?
Authorization = what can you do?
Heute gehst du tiefer in Username/Password-Authentifizierung ein.
2. Der große Authentifizierungsablauf
Wenn sich ein Benutzer mit Benutzername und Passwort anmeldet, sieht der vereinfachte Ablauf so aus:
1. User sends username and password.
2. Spring Security creates an unauthenticated Authentication object.
3. AuthenticationManager receives it.
4. AuthenticationManager delegates to AuthenticationProvider.
5. DaoAuthenticationProvider loads user via UserDetailsService.
6. UserDetailsService loads user from memory or database.
7. PasswordEncoder compares raw password with stored password hash.
8. If valid, authenticated Authentication is created.
9. Authentication is stored in SecurityContext.
10. User is now authenticated for the request.
Kurzversion zum Merken:
Credentials -> AuthenticationManager -> AuthenticationProvider -> UserDetailsService -> PasswordEncoder -> SecurityContext
3. Was ist ein Benutzer in Spring Security?
In deiner Business-Datenbank kann ein Benutzer so aussehen:
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String passwordHash;
private String role;
private boolean enabled;
}
Spring Security verlangt aber nicht direkt deine Entity.
Spring Security braucht eine Security-Darstellung des Benutzers.
Diese Darstellung ist üblicherweise:
UserDetails
Merksatz:
Dein Datenbank-Benutzer ist dein Modell.
UserDetailsist das Benutzermodell von Spring Security.
4. UserDetails
UserDetails repräsentiert die Benutzerinformationen, die für die Authentifizierung benötigt werden.
Es enthält:
username
password
authorities
account status flags
Wichtige Methoden:
String getUsername();
String getPassword();
Collection<? extends GrantedAuthority> getAuthorities();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
Merksatz:
UserDetailssind die Benutzerdaten, die Spring Security für den Login braucht.
5. UserDetails ist nicht zwingend deine Entity
Verwechsle nicht:
UserEntity
UserDetails
UserEntity ist für deine Datenbank.
UserDetails ist für die Spring-Security-Authentifizierung.
Beispiel-Konvertierung:
UserEntity -> UserDetails
Diese Konvertierung passiert üblicherweise in:
UserDetailsService
Merksatz:
UserDetailsServicewandelt deinen gespeicherten Benutzer in Spring-Security-UserDetails um.
6. Eingebaute User-Klasse
Spring Security stellt eine eingebaute Implementierung bereit:
org.springframework.security.core.userdetails.User
Beispiel:
UserDetails userDetails = User.builder()
.username("user@example.com")
.password(passwordHash)
.roles("USER")
.build();
Das ist nützlich, wenn du keine eigene UserDetails-Klasse erstellen willst.
Merksatz:
Spring Securitys
Userist eine einfache eingebauteUserDetails-Implementierung.
7. UserDetailsService
UserDetailsService lädt einen Benutzer anhand des Benutzernamens.
Die Hauptmethode ist:
UserDetails loadUserByUsername(String username);
Kurze Definition:
UserDetailsServiceist der Service, den Spring Security nutzt, um Benutzerdaten während der Username/Password-Authentifizierung zu laden.
Beispiel:
@Service
public class DatabaseUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));
return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}
Merksatz:
UserDetailsServicelädt den Benutzer für die Authentifizierung.
8. Warum die Methode „Username“ heißt
Die Methode heißt:
loadUserByUsername(String username)
Du kannst aber E-Mail als Benutzername nutzen.
Beispiel:
username = email
Das ist also in Ordnung:
loadUserByUsername(String email)
Merksatz:
In Spring Security kann „username“ eine E-Mail, ein Login-Name oder ein anderer eindeutiger Identifier sein.
9. UsernameNotFoundException
Wenn der Benutzer nicht existiert, wirf:
UsernameNotFoundException
Beispiel:
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));
Das sagt Spring Security:
No user found with this username.
Merksatz:
Wenn der Login-Benutzer nicht gefunden wird, wirf
UsernameNotFoundException.
10. GrantedAuthority
Eine Berechtigung ist eine dem Benutzer zugewiesene Erlaubnis.
Beispiel-Berechtigungen:
ROLE_USER
ROLE_ADMIN
TASK_READ
TASK_WRITE
INVOICE_APPROVE
In Spring Security sind Rollen auch Berechtigungen.
Eine Rolle hat üblicherweise das Präfix:
ROLE_
Merksatz:
Berechtigungen sind Erlaubnisse. Rollen sind Berechtigungen mit dem Präfix
ROLE_.
11. Rollen
Wenn du schreibst:
.roles("USER")
erzeugt Spring Security die Berechtigung:
ROLE_USER
Wenn du schreibst:
.roles("ADMIN")
erzeugt Spring Security:
ROLE_ADMIN
Dann funktioniert das:
.hasRole("ADMIN")
weil es prüft auf:
ROLE_ADMIN
Merksatz:
roles("ADMIN")erzeugtROLE_ADMIN.
12. Berechtigungen
Wenn du exakte Berechtigungen willst, kannst du authorities nutzen.
Beispiel:
.authorities("TASK_READ", "TASK_WRITE")
Dann die Security-Regel:
.hasAuthority("TASK_READ")
Rollen sind breit.
Berechtigungen können spezifischer sein.
Beispiel:
ROLE_ADMIN
TASK_READ
TASK_WRITE
TASK_DELETE
INVOICE_APPROVE
Merksatz:
Rollen sind breite Gruppen; Berechtigungen können feingranular sein.
13. Rollen-Falle
Falsch:
User.builder()
.username("admin@example.com")
.password(hash)
.roles("ROLE_ADMIN")
.build();
Warum falsch?
roles() adds ROLE_ automatically.
This can become ROLE_ROLE_ADMIN.
Richtig:
.roles("ADMIN")
oder nutze exakte Berechtigung:
.authorities("ROLE_ADMIN")
Merksatz:
Bei
roles()keinROLE_mit angeben.
14. PasswordEncoder
PasswordEncoder wird zum Hashen und Prüfen von Passwörtern genutzt.
Beispiel-Bean:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Bei der Registrierung:
String passwordHash = passwordEncoder.encode(rawPassword);
Beim Login:
passwordEncoder.matches(rawPassword, storedPasswordHash);
Merksatz:
PasswordEncoderhasht Klartext-Passwörter und prüft Login-Passwörter.
15. Speichere niemals Klartext-Passwörter
Schlechte Datenbankzeile:
email = user@example.com
password = secret123
Gute Datenbankzeile:
email = user@example.com
password_hash = $2a$10$...
Warum?
Wenn die Datenbank geleakt wird:
raw passwords expose users immediately
hashes reduce damage
Merksatz:
Speichere Passwort-Hashes, niemals Klartext-Passwörter.
16. Hashing ist einweg
Passwort-Hashing ist einweg.
Bedeutung:
raw password -> hash
hash -> raw password should not be possible
Beim Login wird das Passwort nicht dekodiert.
Stattdessen:
compare raw password with stored hash using matches()
Beispiel:
boolean ok = passwordEncoder.matches("secret123", storedHash);
Merksatz:
Passwörter werden geprüft, nicht dekodiert.
17. BCrypt
Ein häufiger Password Encoder ist:
BCryptPasswordEncoder
Beispiel:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
BCrypt ist absichtlich langsam.
Warum?
slow hashing makes brute-force attacks harder
Merksatz:
Passwort-Hashing sollte langsam genug sein, um Brute-Force-Angriffe zu erschweren.
18. Warnung zu NoOpPasswordEncoder
Du siehst vielleicht Beispiele mit:
NoOpPasswordEncoder
oder Passwörter wie:
{noop}password
Das bedeutet:
no password hashing
plain text password
Nur für kleine Demos nutzen, niemals in echten Apps.
Merksatz:
noopbedeutet kein Hashing. Nutze es nicht in echten Anwendungen.
19. Authentication
Authentication repräsentiert Authentifizierungsinformationen.
Vor der Authentifizierung:
principal = username
credentials = raw password
authenticated = false
Nach der Authentifizierung:
principal = UserDetails
credentials = usually cleared
authorities = roles/permissions
authenticated = true
Merksatz:
Authenticationkann sowohl den Anmeldeversuch als auch den authentifizierten Benutzer repräsentieren.
20. UsernamePasswordAuthenticationToken
Für Username/Password-Login nutzt Spring Security oft:
UsernamePasswordAuthenticationToken
Vor der Authentifizierung:
new UsernamePasswordAuthenticationToken(email, rawPassword)
Nach der Authentifizierung:
principal = UserDetails
authorities = loaded roles
authenticated = true
Merksatz:
UsernamePasswordAuthenticationTokenträgt Username/Password-Authentifizierungsdaten.
21. AuthenticationManager
AuthenticationManager ist für die Authentifizierung zuständig.
Kurze Definition:
AuthenticationManagernimmt eine Authentifizierungsanfrage entgegen und gibt ein authentifiziertes Ergebnis zurück oder wirft eine Exception.
Beispiel-Mentalmodell:
AuthenticationManager.authenticate(authentication)
Bei Erfolg:
returns authenticated Authentication
Bei Fehlschlag:
throws AuthenticationException
Merksatz:
AuthenticationManagerentscheidet, ob die Authentifizierung erfolgreich ist.
22. AuthenticationProvider
AuthenticationProvider führt eine bestimmte Authentifizierungsstrategie aus.
Beispiele:
username/password
JWT token
LDAP
OAuth2
API key
Für Username/Password ist der übliche Provider:
DaoAuthenticationProvider
Merksatz:
AuthenticationProviderweiß, wie man eine Art von Credential authentifiziert.
23. DaoAuthenticationProvider
DaoAuthenticationProvider authentifiziert Username/Password mit:
UserDetailsService
PasswordEncoder
Ablauf:
1. Receive username/password authentication token.
2. Use UserDetailsService to load user by username.
3. Read stored password hash from UserDetails.
4. Use PasswordEncoder to compare raw password with stored hash.
5. If valid, return authenticated Authentication.
6. If invalid, throw authentication exception.
Merksatz:
DaoAuthenticationProvidernutztUserDetailsServiceplusPasswordEncoder.
24. Vollständiger Username/Password-Authentifizierungsablauf
Detaillierter Ablauf:
1. User submits email and password.
2. Authentication filter reads credentials.
3. Filter creates UsernamePasswordAuthenticationToken.
4. AuthenticationManager receives the token.
5. ProviderManager delegates to DaoAuthenticationProvider.
6. DaoAuthenticationProvider calls UserDetailsService.
7. UserDetailsService loads user from database.
8. DaoAuthenticationProvider checks password with PasswordEncoder.
9. If password is valid, authenticated Authentication is returned.
10. SecurityContextHolder stores Authentication.
11. Authorization rules can now check roles/authorities.
12. Request can continue.
Kurz merken:
Filter -> AuthenticationManager -> DaoAuthenticationProvider -> UserDetailsService -> PasswordEncoder -> SecurityContext
25. Datenbank-User-Entity
Beispiel-Entity:
@Entity
@Table(name = "users")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(name = "password_hash", nullable = false)
private String passwordHash;
@Column(nullable = false)
private String role;
@Column(nullable = false)
private boolean enabled = true;
protected UserEntity() {
}
public UserEntity(String email, String passwordHash, String role) {
this.email = email;
this.passwordHash = passwordHash;
this.role = role;
}
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
public String getPasswordHash() {
return passwordHash;
}
public String getRole() {
return role;
}
public boolean isEnabled() {
return enabled;
}
}
Wichtig:
Database stores password hash, not raw password.
26. User Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByEmail(String email);
boolean existsByEmail(String email);
}
Genutzt für:
load user during login
check duplicate email during registration
Merksatz:
Das User Repository lädt Benutzer aus der Datenbank.
27. Datenbank-UserDetailsService
@Service
public class DatabaseUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));
return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}
Wenn die Datenbank-Rolle ist:
USER
dann erzeugt:
.roles(user.getRole())
die Berechtigung:
ROLE_USER
Wichtig:
Store role as USER or ADMIN if using roles().
Do not store ROLE_USER unless using authorities().
28. Security-Konfiguration mit Datenbank-Benutzern
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Weil du einen UserDetailsService-Bean hast, kann Spring Security ihn für Username/Password-Authentifizierung nutzen.
Merksatz:
Definiere
UserDetailsServiceundPasswordEncoderfür datenbankbasierten Username/Password-Login.
29. Registrierungsablauf
Registrierung bedeutet, einen neuen Benutzer anzulegen.
Request-DTO:
public record RegisterRequest(
@NotBlank
@Email
String email,
@NotBlank
@Size(min = 8)
String password
) {
}
Service:
@Service
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public AuthService(
UserRepository userRepository,
PasswordEncoder passwordEncoder
) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional
public void register(RegisterRequest request) {
if (userRepository.existsByEmail(request.email())) {
throw new EmailAlreadyUsedException(request.email());
}
String passwordHash = passwordEncoder.encode(request.password());
UserEntity user = new UserEntity(
request.email(),
passwordHash,
"USER"
);
userRepository.save(user);
}
}
Merksatz:
Verschlüssele bei der Registrierung das Klartext-Passwort, bevor du es speicherst.
30. Login-Ablauf mit HTTP Basic
Mit HTTP Basic schreibst du normalerweise keinen Login-Controller.
Der Client sendet:
Authorization: Basic base64(email:password)
Spring Security:
extracts credentials
loads user
checks password
sets SecurityContext
authorizes request
Das ist gut zum Lernen und für einfache interne APIs.
Für öffentliche REST APIs ist JWT oder OAuth2 häufiger.
Merksatz:
HTTP-Basic-Login wird von Spring-Security-Filtern verarbeitet, nicht von deinem Controller.
31. Login-Ablauf mit eigenem Login-Endpunkt
Manchmal erstellst du in REST APIs:
POST /api/auth/login
Der Endpunkt nimmt E-Mail und Passwort entgegen, authentifiziert sie und gibt ein Token zurück.
Anfrage:
public record LoginRequest(
@NotBlank
@Email
String email,
@NotBlank
String password
) {
}
Controller:
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
public AuthController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@PostMapping("/login")
public LoginResponse login(@Valid @RequestBody LoginRequest request) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.email(),
request.password()
)
);
return new LoginResponse(
authentication.getName(),
"token-will-come-later"
);
}
}
Response:
public record LoginResponse(
String username,
String token
) {
}
Wichtig:
This checks username/password using Spring Security.
Later, JWT lesson will replace token-will-come-later with real JWT.
32. AuthenticationManager bereitstellen
In vielen modernen Konfigurationen kannst du AuthenticationManager so bereitstellen:
@Bean
AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration
) throws Exception {
return configuration.getAuthenticationManager();
}
Vollständiger Konfigurationsteil:
@Configuration
public class SecurityConfig {
@Bean
AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration
) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Merksatz:
Ein eigener Login-Endpunkt kann
AuthenticationManagerzur Authentifizierung von Credentials nutzen.
33. Was passiert bei falschem Passwort?
Der Benutzer gibt ein falsches Passwort ein.
Ablauf:
1. UserDetailsService loads user.
2. PasswordEncoder.matches(raw, hash) returns false.
3. Authentication fails.
4. Spring Security throws authentication exception.
5. Response is usually 401.
Merksatz:
Falsches Passwort bedeutet Authentifizierungsfehler, normalerweise 401.
34. Deaktivierter Benutzer
Wenn der Benutzer deaktiviert ist:
.disabled(!user.isEnabled())
dann kann Spring Security den Login ablehnen.
Datenbank:
enabled = false
Ergebnis:
authentication fails
Merksatz:
UserDetails-Kontoflags können den Login verhindern.
35. Kontostatus-Flags
UserDetails unterstützt:
enabled
account non-expired
account non-locked
credentials non-expired
Bedeutung:
| Flag | Bedeutung |
|---|---|
| enabled | Benutzer ist aktiv |
| account non-expired | Konto ist nicht abgelaufen |
| account non-locked | Konto ist nicht gesperrt |
| credentials non-expired | Passwort ist nicht abgelaufen |
In vielen einfachen Apps wird zuerst nur enabled genutzt.
Merksatz:
Benutzerkonto-Flags können die Authentifizierung blockieren.
36. Eigene UserDetails-Klasse
Manchmal brauchst du die User-ID im Principal.
Erstelle einen eigenen Principal:
public class AppUserPrincipal implements UserDetails {
private final Long id;
private final String email;
private final String passwordHash;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean enabled;
public AppUserPrincipal(
Long id,
String email,
String passwordHash,
Collection<? extends GrantedAuthority> authorities,
boolean enabled
) {
this.id = id;
this.email = email;
this.passwordHash = passwordHash;
this.authorities = authorities;
this.enabled = enabled;
}
public Long getId() {
return id;
}
@Override
public String getUsername() {
return email;
}
@Override
public String getPassword() {
return passwordHash;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
Dann:
return new AppUserPrincipal(
user.getId(),
user.getEmail(),
user.getPasswordHash(),
List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole())),
user.isEnabled()
);
Merksatz:
Eigene
UserDetailssind nützlich, wenn du zusätzliche Principal-Daten wie die User-ID brauchst.
37. Eigenen Principal im Controller holen
Wenn der Principal custom ist:
@GetMapping("/api/me")
public MeDto me(@AuthenticationPrincipal AppUserPrincipal principal) {
return new MeDto(
principal.getId(),
principal.getUsername()
);
}
DTO:
public record MeDto(
Long id,
String email
) {
}
Merksatz:
@AuthenticationPrincipalinjiziert den aktuellen Principal.
38. Principal vs. Authentication vs. @AuthenticationPrincipal
Controller-Optionen:
@GetMapping("/api/me1")
public String me1(Principal principal) {
return principal.getName();
}
@GetMapping("/api/me2")
public String me2(Authentication authentication) {
return authentication.getName();
}
@GetMapping("/api/me3")
public MeDto me3(@AuthenticationPrincipal AppUserPrincipal principal) {
return new MeDto(principal.getId(), principal.getUsername());
}
Nutze:
Principal für einfachen Benutzernamen
Authentication für Berechtigungen/Details
@AuthenticationPrincipal für eigene Principal-Daten
39. SecurityContext im Service
Manchmal braucht der Service den aktuellen Benutzer.
Einfache Option:
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
String email = authentication.getName();
Aber verteile das nicht überall.
Besseres Design:
Controller liest aktuellen Principal
übergibt User-ID/E-Mail an den Service
oder nutze eine kleine CurrentUserService-Abstraktion
Beispiel:
@Service
public class CurrentUserService {
public String currentUsername() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
return authentication.getName();
}
}
Merksatz:
Vermeide willkürliche
SecurityContextHolder-Aufrufe überall; zentralisiere den Zugriff auf den aktuellen Benutzer.
40. Security- und Datenbank-Ablauf — Beispiel
Endpunkt:
GET /api/tasks
Authorization: Basic ...
Ablauf:
1. BasicAuthenticationFilter reads Authorization header.
2. It creates username/password authentication request.
3. AuthenticationManager authenticates.
4. DaoAuthenticationProvider loads user with UserDetailsService.
5. UserRepository finds user by email.
6. PasswordEncoder checks password.
7. Authentication is stored in SecurityContext.
8. Authorization checks endpoint rules.
9. Controller runs.
10. Controller/service can read current user.
Merksatz:
Login nutzt das User Repository indirekt über
UserDetailsService.
41. Schlechte Praxis: Passwort-Hash zurückgeben
Schlechtes DTO:
public record UserDto(
Long id,
String email,
String passwordHash
) {
}
Gib niemals Passwort-Hashes in API-Responses zurück.
Besser:
public record UserDto(
Long id,
String email,
String role
) {
}
Merksatz:
Gib keine Passwort-Hashes an Clients zurück.
42. Schlechte Praxis: Passwörter loggen
Schlecht:
log.info("Login request: {}", request);
Wenn der Request ein Passwort enthält, kann das Klartext-Passwort geloggt werden.
Besser:
log.info("Login attempt for email={}", request.email());
Merksatz:
Logge niemals Klartext-Passwörter.
43. Schlechte Praxis: Benutzer ohne Passwort-Encoding registrieren
Schlecht:
UserEntity user = new UserEntity(
request.email(),
request.password(),
"USER"
);
Das speichert das Klartext-Passwort.
Richtig:
String passwordHash = passwordEncoder.encode(request.password());
UserEntity user = new UserEntity(
request.email(),
passwordHash,
"USER"
);
Merksatz:
Verschlüssele Passwörter immer vor dem Speichern.
44. Schlechte Praxis: Manuelle Passwortprüfung im Controller
Schlecht:
@PostMapping("/login")
public String login(@RequestBody LoginRequest request) {
UserEntity user = userRepository.findByEmail(request.email()).orElseThrow();
if (!user.getPasswordHash().equals(request.password())) {
throw new RuntimeException("Bad password");
}
return "ok";
}
Probleme:
vergleicht Klartext-Passwort falsch
umgeht PasswordEncoder
umgeht Spring-Security-Authentifizierungsablauf
schlechte Fehlerbehandlung
unsicher
Richtig:
Nutze AuthenticationManager oder Spring-Security-Filter.
Merksatz:
Vergleiche Passwörter nicht manuell in Controllern.
45. Typische Prüfungsfallen
Falle 1
UserDetailsService lädt Benutzerdaten; es prüft Passwörter nicht selbst.
Falle 2
DaoAuthenticationProvider nutzt UserDetailsService und PasswordEncoder.
Falle 3
PasswordEncoder ist einweg. Es dekodiert keine Passwörter.
Falle 4
Speichere Passwort-Hashes, keine Klartext-Passwörter.
Falle 5
roles("ADMIN") erzeugt ROLE_ADMIN.
Falle 6
Nutze nicht roles("ROLE_ADMIN").
Falle 7
hasRole("ADMIN") prüft auf ROLE_ADMIN.
Falle 8
hasAuthority("ROLE_ADMIN") prüft exakt ROLE_ADMIN.
Falle 9
UsernameNotFoundException wird verwendet, wenn der Benutzer nicht geladen werden kann.
Falle 10
Falsches Passwort führt meist zu Authentifizierungsfehler und 401.
Falle 11
Datenbank-User-Entity und UserDetails sind nicht dasselbe.
Falle 12
AuthenticationManager authentifiziert Credentials.
Falle 13
AuthenticationProvider führt eine bestimmte Authentifizierungsstrategie aus.
Falle 14
Eigene Login-Endpunkte können AuthenticationManager nutzen.
Falle 15
Gib niemals Passwort-Hashes zurück und logge keine Klartext-Passwörter.
46. Echte Prüfungsfrage: UserDetails
Frage:
Was ist UserDetails?
Antwort:
UserDetails ist die Darstellung von Benutzerinformationen in Spring Security, die für die Authentifizierung benötigt werden — zum Beispiel Benutzername, Passwort, Berechtigungen und Kontostatus-Flags.
47. Echte Prüfungsfrage: UserDetailsService
Frage:
Was macht UserDetailsService?
Antwort:
UserDetailsService lädt einen Benutzer anhand des Benutzernamens und gibt ein UserDetails-Objekt für die Authentifizierung zurück.
48. Echte Prüfungsfrage: PasswordEncoder
Frage:
Wofür wird PasswordEncoder genutzt?
Antwort:
PasswordEncoder hasht Klartext-Passwörter und prüft Login-Passwörter gegen gespeicherte Passwort-Hashes.
49. Echte Prüfungsfrage: AuthenticationManager
Frage:
Was macht AuthenticationManager?
Antwort:
AuthenticationManager nimmt eine Authentifizierungsanfrage entgegen und gibt bei gültigen Credentials ein authentifiziertes Authentication zurück — oder wirft bei ungültigen Credentials eine Authentifizierungs-Exception.
50. Echte Prüfungsfrage: DaoAuthenticationProvider
Frage:
Was nutzt DaoAuthenticationProvider?
Antwort:
Es nutzt UserDetailsService, um den Benutzer zu laden, und PasswordEncoder, um das Passwort zu prüfen.
51. Echte Prüfungsfrage: Rollen-Präfix
Frage:
Was erzeugt roles("ADMIN")?
Antwort:
Es erzeugt die Berechtigung ROLE_ADMIN.
52. Echte Prüfungsfrage: hasRole
Frage:
Was prüft hasRole("ADMIN")?
Antwort:
Es prüft, ob der Benutzer die Berechtigung ROLE_ADMIN hat.
53. Echte Prüfungsfrage: Passwort-Hashing
Frage:
Sollen Passwörter im Klartext oder gehasht gespeichert werden?
Antwort:
Passwörter sollten als Hashes gespeichert werden, niemals als Klartext.
54. Echte Prüfungsfrage: @AuthenticationPrincipal
Frage:
Was macht @AuthenticationPrincipal?
Antwort:
Es injiziert den aktuell authentifizierten Principal in einen Controller-Methodenparameter.
55. Interview-Antwort
Frage:
Erkläre den Username/Password-Authentifizierungsablauf in Spring Security.
Gute Antwort:
Wenn ein Benutzer Benutzername und Passwort sendet, erstellt Spring Security eine Authentifizierungsanfrage — meist ein UsernamePasswordAuthenticationToken. Der AuthenticationManager nimmt sie entgegen und delegiert an einen AuthenticationProvider, üblicherweise DaoAuthenticationProvider. Der Provider nutzt UserDetailsService, um den Benutzer zu laden, und PasswordEncoder, um das Klartext-Passwort mit dem gespeicherten Passwort-Hash zu vergleichen. Bei erfolgreicher Authentifizierung erstellt Spring Security ein authentifiziertes Authentication und speichert es im SecurityContext.
56. Interview-Antwort
Frage:
Welche Rolle hat
UserDetailsService?
Gute Antwort:
UserDetailsService lädt Benutzerinformationen anhand des Benutzernamens. In einer datenbankbasierten Anwendung fragt es üblicherweise das User Repository ab, findet den Benutzer per E-Mail oder Benutzername und wandelt den Datenbank-Benutzer in ein UserDetails-Objekt mit Benutzername, Passwort-Hash, Rollen, Berechtigungen und Kontostatus-Flags um.
57. Interview-Antwort
Frage:
Warum brauchen wir
PasswordEncoder?
Gute Antwort:
Du brauchst PasswordEncoder, weil Passwörter niemals als Klartext gespeichert werden sollten. Ein Password Encoder hasht das Passwort vor dem Speichern und prüft später ein Login-Passwort gegen den gespeicherten Hash. Das ist einweg — es dekodiert keine Passwörter. Das reduziert das Risiko, wenn die Datenbank geleakt wird.
58. Interview-Antwort
Frage:
Was ist der Unterschied zwischen Rolle und Berechtigung?
Gute Antwort:
Eine Berechtigung ist eine Erlaubnis in Spring Security. Eine Rolle ist eine spezielle Berechtigung mit dem Präfix ROLE_. Zum Beispiel erzeugt roles("ADMIN") die Berechtigung ROLE_ADMIN, und hasRole("ADMIN") prüft auf ROLE_ADMIN. Für feingranulare Erlaubnisse kannst du Berechtigungen wie TASK_READ oder INVOICE_APPROVE nutzen.
59. Interview-Antwort
Frage:
Wie würdest du Datenbank-Login implementieren?
Gute Antwort:
Du speicherst Benutzer in einer Datenbank mit eindeutiger E-Mail, Passwort-Hash, Rolle oder Berechtigungen und enabled-Flag. Du implementierst UserDetailsService, um den Benutzer aus dem Repository zu laden und UserDetails zurückzugeben. Du definierst einen PasswordEncoder, meist BCrypt. Dann kann Spring Securitys DaoAuthenticationProvider UserDetailsService und PasswordEncoder für Username/Password-Login nutzen.
60. Kleine Code-Übung
Erstelle Repository:
public interface UserRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByEmail(String email);
}
Erstelle UserDetailsService:
@Service
public class DatabaseUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));
return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}
Fragen:
- Was gibt
findByEmailzurück? - Was passiert, wenn der Benutzer nicht gefunden wird?
- Was erwartet
.password(...)? - Was erzeugt
.roles("USER")? - Was macht
.disabled(!user.isEnabled())?
Antworten:
Optional<UserEntity>.- Es wird
UsernameNotFoundExceptiongeworfen. - Den gespeicherten, encodierten Passwort-Hash.
- Die Berechtigung
ROLE_USER. - Es deaktiviert den Login, wenn der Benutzer nicht enabled ist.
61. Kleine Bug-Übung 1
Problem:
return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles("ROLE_ADMIN")
.build();
Frage:
Was ist falsch?
Antwort:
roles() fügt das Präfix ROLE_ automatisch hinzu. Das kann ROLE_ROLE_ADMIN erzeugen.
Richtig:
.roles("ADMIN")
oder:
.authorities("ROLE_ADMIN")
62. Kleine Bug-Übung 2
Problem:
String passwordHash = request.password();
userRepository.save(new UserEntity(request.email(), passwordHash, "USER"));
Frage:
Was ist falsch?
Antwort:
Das Klartext-Passwort wird direkt gespeichert. Es muss zuerst encodiert werden.
Richtig:
String passwordHash = passwordEncoder.encode(request.password());
userRepository.save(new UserEntity(request.email(), passwordHash, "USER"));
63. Kleine Bug-Übung 3
Problem:
if (user.getPasswordHash().equals(request.password())) {
return "login ok";
}
Frage:
Was ist falsch?
Antwort:
Das vergleicht einen gespeicherten Hash mit einem Klartext-Passwort und umgeht PasswordEncoder. Nutze passwordEncoder.matches(rawPassword, storedHash) — oder besser AuthenticationManager.
Übungsfragen und Antworten
Frage 1
Was ist UserDetails?
Antwort:
UserDetails ist die Darstellung von Benutzerdaten in Spring Security, die für die Authentifizierung benötigt werden — einschließlich Benutzername, Passwort, Berechtigungen und Kontostatus-Flags.
Frage 2
Was ist UserDetailsService?
Antwort:
UserDetailsService lädt Benutzerdaten anhand des Benutzernamens und gibt UserDetails zurück.
Frage 3
Was macht loadUserByUsername?
Antwort:
Es findet einen Benutzer anhand des Benutzernamens und wandelt ihn in UserDetails um.
Frage 4
Kann der Benutzername eine E-Mail sein?
Antwort:
Ja. In vielen Apps wird die E-Mail als Benutzername genutzt.
Frage 5
Welche Exception solltest du werfen, wenn der Benutzer nicht gefunden wird?
Antwort:
UsernameNotFoundException.
Frage 6
Was ist PasswordEncoder?
Antwort:
PasswordEncoder hasht Klartext-Passwörter und prüft sie gegen gespeicherte Hashes.
Frage 7
Warum sollten Passwörter gehasht werden?
Antwort:
Weil Klartext-Passwörter bei einem Leak gefährlich sind. Hashing reduziert den Schaden und verhindert, das echte Passwort zu speichern.
Frage 8
Dekodiert PasswordEncoder Passwörter?
Antwort:
Nein. Passwort-Encoding ist einweg. Es prüft mit matches.
Frage 9
Was ist AuthenticationManager?
Antwort:
AuthenticationManager authentifiziert eine Authentifizierungsanfrage und gibt ein authentifiziertes Ergebnis zurück oder wirft eine Exception.
Frage 10
Was ist AuthenticationProvider?
Antwort:
AuthenticationProvider führt eine bestimmte Authentifizierungsstrategie aus.
Frage 11
Was ist DaoAuthenticationProvider?
Antwort:
DaoAuthenticationProvider ist der übliche Username/Password-Provider, der UserDetailsService und PasswordEncoder nutzt.
Frage 12
Was nutzt DaoAuthenticationProvider?
Antwort:
Es nutzt UserDetailsService, um den Benutzer zu laden, und PasswordEncoder, um das Passwort zu prüfen.
Frage 13
Was erzeugt roles("ADMIN")?
Antwort:
Es erzeugt die Berechtigung ROLE_ADMIN.
Frage 14
Was prüft hasRole("ADMIN")?
Antwort:
Es prüft auf die Berechtigung ROLE_ADMIN.
Frage 15
Was ist der Unterschied zwischen Rolle und Berechtigung?
Antwort:
Eine Berechtigung ist eine Erlaubnis. Eine Rolle ist meist eine Berechtigung mit dem Präfix ROLE_.
Frage 16
Was ist UsernamePasswordAuthenticationToken?
Antwort:
Es ist eine Authentication-Implementierung, die üblicherweise Username/Password-Authentifizierungsdaten trägt.
Frage 17
Was passiert bei falschem Passwort?
Antwort:
Die Authentifizierung schlägt fehl, und die Response ist meist 401.
Frage 18
Was ist @AuthenticationPrincipal?
Antwort:
@AuthenticationPrincipal injiziert den aktuell authentifizierten Principal in eine Controller-Methode.
Frage 19
Warum solltest du Login-Request-Objekte nicht direkt loggen?
Antwort:
Weil das Objekt Klartext-Passwörter oder sensible Daten enthalten kann.
Frage 20
Wie sieht der vollständige Datenbank-Login-Ablauf aus?
Antwort:
Der Benutzer sendet Benutzername/Passwort. Spring Security erstellt eine Authentifizierungsanfrage. AuthenticationManager delegiert an DaoAuthenticationProvider. Der Provider nutzt UserDetailsService, um den Benutzer aus der Datenbank zu laden, und PasswordEncoder, um das Passwort zu prüfen. Bei Erfolg wird ein authentifiziertes Authentication im SecurityContext gespeichert.
Merksätze zum Mitnehmen
UserDetailsist die Benutzerdarstellung von Spring Security.UserDetailsServicelädt einen Benutzer anhand des Benutzernamens.- Der Benutzername kann eine E-Mail sein.
- Wirf
UsernameNotFoundException, wenn der Benutzer fehlt. PasswordEncoderhasht und prüft Passwörter.- Passwort-Hashing ist einweg.
- Speichere Passwort-Hashes, niemals Klartext-Passwörter.
- BCrypt ist ein häufiger Password Encoder.
AuthenticationManagerauthentifiziert Credentials.AuthenticationProviderführt eine bestimmte Authentifizierungsstrategie aus.DaoAuthenticationProvidernutztUserDetailsServiceundPasswordEncoder.Authenticationrepräsentiert den Anmeldeversuch oder den authentifizierten Benutzer.UsernamePasswordAuthenticationTokenträgt Username/Password-Daten.roles("ADMIN")erzeugtROLE_ADMIN.hasRole("ADMIN")prüftROLE_ADMIN.- Nutze nicht
roles("ROLE_ADMIN"). - Nutze
@AuthenticationPrincipal, um den aktuellen Principal zu injizieren. - Gib niemals Passwort-Hashes zurück.
- Logge niemals Klartext-Passwörter.
- Verschlüssele Passwörter bei der Registrierung.
- Nutze
AuthenticationManagerfür eigene Login-Endpunkte.