Zum Hauptinhalt springen

Woche 6, Tag 3 — Authorization im Detail

Ziel

Heute verstehst du Autorisierung in Spring Security.

Die Kernfragen:

  1. Was ist Autorisierung?
  2. Was ist der Unterschied zwischen Rollen und Berechtigungen?
  3. Was prüft hasRole?
  4. Was prüft hasAuthority?
  5. Wie funktionieren URL-Autorisierungsregeln?
  6. Warum ist die Reihenfolge der Regeln wichtig?
  7. Was ist Method Security?
  8. Was ist @EnableMethodSecurity?
  9. Was ist @PreAuthorize?
  10. Wie verwendest du Methodenparameter in Security-Ausdrücken?
  11. Was sind Berechtigungen auf Datenebene?
  12. Warum reichen URL-Regeln nicht aus?
  13. Was sind typische Prüfungsfallen?

1. Kurz-Wiederholung aus Woche 6, Tag 2

An Tag 2 hast du gelernt:

  • UserDetails ist die Benutzerdarstellung von Spring Security.
  • UserDetailsService lädt Benutzer anhand des Benutzernamens.
  • Der Benutzername kann eine E-Mail sein.
  • PasswordEncoder hasht und prüft Passwörter.
  • Speichere Passwort-Hashes, keine Klartext-Passwörter.
  • AuthenticationManager authentifiziert Anmeldedaten.
  • DaoAuthenticationProvider nutzt UserDetailsService und PasswordEncoder.
  • Authentication repräsentiert den Anmeldeversuch oder den authentifizierten Benutzer.
  • roles("ADMIN") erzeugt ROLE_ADMIN.
  • hasRole("ADMIN") prüft ROLE_ADMIN.

Merksatz:


Authentication = who are you?
Authorization = what can you do?

Heute gehst du tiefer in Autorisierung ein.


2. Was ist Autorisierung?

Autorisierung beantwortet:


What is this user allowed to do?

Beispiele:


Can this user access /api/admin/users?
Can this user delete a task?
Can this user approve an invoice?
Can this user access client 10?
Can this user see another tenant's data?
Can this user export payroll documents?

Autorisierung passiert nach der Authentifizierung.

Zuerst:


Who is the user?

Dann:


What may this user do?

Merksatz:

Autorisierung prüft Berechtigungen nach der Authentifizierung.


3. Authentifizierung vs. Autorisierung

KonzeptFrageBeispiel
AuthentifizierungWer bist du?Login mit E-Mail und Passwort
AutorisierungWas darfst du tun?Nur ADMIN darf Benutzer löschen

Beispiel:


User logs in successfully.
Authentication succeeded.

User tries to call /api/admin/users.
Authorization checks if user has ADMIN role.

Merksatz:


Authentication proves identity.
Authorization checks access.

4. 401 vs. 403 — Kurzüberblick

401 Unauthorized

Bedeutet:


The user is not authenticated.

Beispiele:


missing token
invalid token
wrong password
not logged in

403 Forbidden

Bedeutet:


The user is authenticated but not allowed.

Beispiele:


USER tries to access ADMIN endpoint
employee tries to access another tenant
normal user tries to delete another user's task

Merksatz:


401 = who are you?
403 = I know who you are, but you are not allowed.

5. Was sind Berechtigungen (Authorities)?

Eine Berechtigung ist eine dem Benutzer zugewiesene Erlaubnis.

Beispiele:


ROLE_USER
ROLE_ADMIN
TASK_READ
TASK_WRITE
TASK_DELETE
INVOICE_APPROVE
CLIENT_EXPORT

Spring Security speichert Berechtigungen in:


Authentication

Beispiel:


principal = user@example.com
authorities = ROLE_USER, TASK_READ, TASK_WRITE

Merksatz:

Berechtigungen sind die Erlaubnisse, die Spring Security kennt.


6. Was sind Rollen?

Eine Rolle ist meist eine breite Gruppe von Berechtigungen.

Beispiele:


USER
ADMIN
MANAGER
ACCOUNTANT
CONSULTANT

In Spring Security werden Rollen meist als Berechtigungen mit Präfix dargestellt:


ROLE_

Die Rolle:


ADMIN

wird zur Berechtigung:


ROLE_ADMIN

Merksatz:

Eine Rolle ist meist eine Berechtigung mit dem Präfix ROLE_.


7. Rolle vs. Berechtigung

ThemaRolleBerechtigung
Bedeutungbreite Gruppekonkrete Erlaubnis
BeispielADMINTASK_DELETE
GespeichertROLE_ADMINTASK_DELETE
Typische PrüfunghasRole("ADMIN")hasAuthority("TASK_DELETE")
Einsatzeinfache Apps, grober Zugrifffeingranulare Berechtigungen

Merksatz:


Role = group.
Authority = permission.

8. hasRole

Beispiel:


.requestMatchers("/api/admin/**").hasRole("ADMIN")

Das prüft, ob der Benutzer die Berechtigung hat:


ROLE_ADMIN

Wichtig:


hasRole("ADMIN")

prüft nicht die Berechtigung:


ADMIN

Sondern:


ROLE_ADMIN

Merksatz:

hasRole("ADMIN") prüft auf ROLE_ADMIN.


9. hasAuthority

Beispiel:


.requestMatchers("/api/tasks/**").hasAuthority("TASK_READ")

Das prüft exakt:


TASK_READ

Weiteres Beispiel:


.requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")

Das prüft exakt:


ROLE_ADMIN

Merksatz:

hasAuthority prüft die exakte Berechtigungszeichenkette.


10. hasRole vs. hasAuthority

CodePrüft auf
hasRole("ADMIN")ROLE_ADMIN
hasAuthority("ROLE_ADMIN")ROLE_ADMIN
hasAuthority("TASK_READ")TASK_READ
hasRole("ROLE_ADMIN")meist falsch

Häufiger Fehler:


hasRole("ROLE_ADMIN")

Warum falsch?


hasRole adds ROLE_ automatically.
This can become ROLE_ROLE_ADMIN.

Richtig:


hasRole("ADMIN")

oder:


hasAuthority("ROLE_ADMIN")

Merksatz:

Nutze hasRole("ADMIN") oder hasAuthority("ROLE_ADMIN"), nicht hasRole("ROLE_ADMIN").


11. Woher kommen Berechtigungen?

Berechtigungen kommen meist aus:


database roles
database permissions
JWT claims
OAuth2 scopes
LDAP groups
hardcoded in-memory users
custom AuthenticationProvider

Beispiel mit Datenbankbenutzer:


return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.build();

Wenn user.getRole() zurückgibt:


ADMIN

erzeugt Spring:


ROLE_ADMIN

Merksatz:

Autorisierung hängt von den während der Authentifizierung geladenen Berechtigungen ab.


12. Einfache URL-Autorisierung

Security-Konfiguration:


@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}

Bedeutung:


/api/public/** is open
/api/admin/** needs ADMIN role
everything else needs login

Merksatz:

URL-Autorisierung schützt Endpunkte nach Pfad und HTTP-Methode.


13. HTTP-Methoden-Autorisierung

Beispiel:


.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/tasks/**").hasAuthority("TASK_READ")
.requestMatchers(HttpMethod.POST, "/api/tasks/**").hasAuthority("TASK_WRITE")
.requestMatchers(HttpMethod.DELETE, "/api/tasks/**").hasAuthority("TASK_DELETE")
.anyRequest().authenticated()
)

Bedeutung:


GET task endpoints need TASK_READ
POST task endpoints need TASK_WRITE
DELETE task endpoints need TASK_DELETE

Das ist präziser als reine Pfadregeln.

Merksatz:

URL-Regeln können sowohl Pfad als auch HTTP-Methode matchen.


14. Die Reihenfolge der Regeln ist wichtig

Gut:


.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)

Schlecht:


.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
.requestMatchers("/api/public/**").permitAll()
)

Warum schlecht?


anyRequest matches everything.
Rules after anyRequest are unreachable or invalid.

Merksatz:

Spezifische Regeln zuerst. Allgemeine Regeln zuletzt.


15. permitAll, authenticated, denyAll

Häufige Regeln:


permitAll()
authenticated()
denyAll()

Bedeutung:

RegelBedeutung
permitAll()jeder darf zugreifen
authenticated()Benutzer muss angemeldet sein
denyAll()niemand darf zugreifen

Beispiele:


.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/internal/**").denyAll()
.anyRequest().authenticated()

16. hasAnyRole und hasAnyAuthority

Beispiel:


.requestMatchers("/api/reports/**").hasAnyRole("ADMIN", "MANAGER")

Prüft auf:


ROLE_ADMIN or ROLE_MANAGER

Beispiel:


.requestMatchers("/api/tasks/**").hasAnyAuthority("TASK_READ", "TASK_WRITE")

Prüft auf:


TASK_READ or TASK_WRITE

Merksatz:

hasAnyRole / hasAnyAuthority bedeutet: mindestens eine davon reicht.


17. URL-Autorisierung ist grob

URL-Regeln eignen sich gut für breiten Zugriff.

Beispiele:


/api/admin/** needs ADMIN
/api/auth/** is public
GET /api/tasks/** needs TASK_READ
POST /api/tasks/** needs TASK_WRITE

Aber URL-Regeln können oft nicht beantworten:


Can this user access client 10?
Does this task belong to the user's tenant?
Can this accountant approve this invoice?
Is this user the owner of this record?

Das sind Berechtigungen auf Datenebene.

Merksatz:

URL-Autorisierung schützt Endpunkte, aber nicht immer konkrete Daten.


18. Berechtigung auf Datenebene

Berechtigung auf Datenebene bedeutet:

Der Zugriff hängt von den konkreten Daten ab, auf die zugegriffen wird.

Beispiel:


GET /api/clients/10/tasks

Eine URL-Regel kann prüfen:


user is authenticated
user has role USER

Aber der Service muss prüfen:


Does this user belong to client 10?
Does this user belong to the same tenant?
Is this user allowed to see these tasks?

Merksatz:

Berechtigungen auf Datenebene prüfen den Zugriff auf eine konkrete Ressource, nicht nur auf eine URL.


19. Mandanten-Berechtigungsbeispiel

Stell dir eine Multi-Tenant-App vor.

Benutzer:


userId = 5
tenantId = 100
role = USER

Task:


taskId = 20
tenantId = 200

Auch wenn der Benutzer authentifiziert ist und die Rolle USER hat:


user should not access task from tenant 200

Warum?


different tenant

Merksatz:

In Multi-Tenant-Apps muss Autorisierung die Mandanten-Zugehörigkeit prüfen.


20. Berechtigungsprüfung auf Service-Ebene

Beispiel:


@Service
public class TaskService {

private final TaskRepository taskRepository;
private final PermissionService permissionService;

public TaskService(
TaskRepository taskRepository,
PermissionService permissionService
) {
this.taskRepository = taskRepository;
this.permissionService = permissionService;
}

@Transactional(readOnly = true)
public TaskDto findById(Long taskId, AppUserPrincipal currentUser) {
TaskEntity task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

if (!permissionService.canAccessTask(currentUser, task)) {
throw new AccessDeniedException("No access to this task");
}

return toDto(task);
}
}

Das prüft die echte Geschäftsberechtigung.

Merksatz:

Service-Methoden sollten den Zugriff auf Geschäftsdaten schützen.


21. AccessDeniedException

Wenn ein authentifizierter Benutzer nicht berechtigt ist, wirf:


AccessDeniedException

Beispiel:


throw new AccessDeniedException("No access to this task");

Spring Security mappt das üblicherweise auf:


403 Forbidden

Merksatz:

AccessDeniedException bedeutet: authentifiziert, aber nicht erlaubt.


22. Method Security

Method Security bedeutet:

Autorisierungsregeln werden auf Methoden gelegt, meist auf Service-Methoden.

Beispiel:


@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

Diese Methode darf nur laufen, wenn der Benutzer die Rolle ADMIN hat.

Merksatz:

Method Security schützt Methodenaufrufe, nicht nur URLs.


23. Method Security aktivieren

Um Annotationen wie @PreAuthorize zu nutzen, aktiviere Method Security:


@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

Oder setze es auf eine bestehende Security-Konfiguration:


@Configuration
@EnableMethodSecurity
public class SecurityConfig {
}

Wichtig:


Adding spring-boot-starter-security does not automatically enable method security.

Merksatz:

Nutze @EnableMethodSecurity, um Method-Security-Annotationen zu aktivieren.


24. @PreAuthorize

@PreAuthorize prüft die Autorisierung, bevor die Methode läuft.

Beispiel:


@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

Wenn der Benutzer ADMIN hat:


method runs

Wenn der Benutzer ADMIN nicht hat:


method is blocked
AccessDeniedException

Merksatz:

@PreAuthorize prüft vor der Methodenausführung.


25. @PreAuthorize mit Berechtigung

Beispiel:


@PreAuthorize("hasAuthority('TASK_DELETE')")
public void deleteTask(Long taskId) {
taskRepository.deleteById(taskId);
}

Das prüft die exakte Berechtigung:


TASK_DELETE

Wenn der Benutzer nur hat:


TASK_READ

wird die Methode blockiert.


26. @PreAuthorize mit mehreren Rollen

Beispiel:


@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<ReportDto> getReports() {
return reportRepository.findAllReports();
}

Bedeutung:


ADMIN or MANAGER can call this method

Weiteres Beispiel:


@PreAuthorize("hasRole('ADMIN') or hasAuthority('REPORT_READ')")
public List<ReportDto> getReports() {
return reportRepository.findAllReports();
}

Merksatz:

@PreAuthorize unterstützt Security-Ausdrücke.


27. Methodenparameter in @PreAuthorize

@PreAuthorize kann Methodenparameter nutzen.

Beispiel:


@PreAuthorize("#userId == authentication.principal.id")
public UserDto getOwnProfile(Long userId) {
return userService.findById(userId);
}

Bedeutung:


user can access only their own profile

Weiteres Beispiel:


@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public UserDto getProfile(Long userId) {
return userService.findById(userId);
}

Bedeutung:


ADMIN can access any profile
normal user can access only own profile

Merksatz:

#parameterName bezieht sich auf ein Methodenargument in Security-Ausdrücken.


28. Eigene Berechtigungsmethode in @PreAuthorize

Für komplexe Prüfungen rufst du eine Spring-Bean auf.

Permission Service:


@Component("permissionService")
public class PermissionService {

public boolean canAccessTask(Long taskId, Authentication authentication) {
String username = authentication.getName();

// load user/task and check tenant, ownership, role, etc.
return true;
}
}

Service-Methode:


@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
public TaskDto findTask(Long taskId) {
return taskRepository.findById(taskId)
.map(this::toDto)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));
}

Bedeutung:


Before method runs, Spring calls permissionService.canAccessTask(...)

Merksatz:

Für echte Geschäftsberechtigungen rufst du eine Permission-Bean aus @PreAuthorize auf.


29. authentication in Ausdrücken

Innerhalb von @PreAuthorize kannst du nutzen:


authentication

Beispiel:


@PreAuthorize("authentication.name == #email")
public UserDto findByEmail(String email) {
return userService.findByEmail(email);
}

authentication.name liefert meist:


current username

Wenn der Benutzername die E-Mail ist, dann:


authentication.name = current user's email

Merksatz:

authentication gibt dir Zugriff auf den aktuell authentifizierten Benutzer in Ausdrücken.


30. Eigener Principal in Ausdrücken

Wenn der Principal eigen ist:


public class AppUserPrincipal implements UserDetails {

private final Long id;
private final Long tenantId;
private final String email;

public Long getId() {
return id;
}

public Long getTenantId() {
return tenantId;
}

@Override
public String getUsername() {
return email;
}
}

Dann der Ausdruck:


@PreAuthorize("#tenantId == authentication.principal.tenantId")
public List<TaskDto> findTenantTasks(Long tenantId) {
return taskRepository.findByTenantId(tenantId)
.stream()
.map(this::toDto)
.toList();
}

Bedeutung:


User can only access their own tenant.

Merksatz:

Eigene Principal-Felder kannst du in Method-Security-Ausdrücken nutzen.


31. @PostAuthorize

@PostAuthorize prüft nach dem Rückgabewert der Methode.

Beispiel:


@PostAuthorize("returnObject.ownerEmail == authentication.name")
public DocumentDto findDocument(Long id) {
return documentRepository.findById(id)
.map(this::toDto)
.orElseThrow(() -> new ResourceNotFoundException("Document", id));
}

Bedeutung:


method runs first
then Spring checks returned object
only owner can receive it

Vorsichtig nutzen.

Warum?


the data is loaded before authorization decision
may be less efficient
may have side effects if used on write methods

Merksatz:

@PostAuthorize prüft nach der Methodenausführung.


32. Meist @PreAuthorize bevorzugen

Meist bevorzugst du:


@PreAuthorize(...)

weil:


it blocks method before execution
avoids unnecessary database work
avoids side effects
clearer for write operations

Nutze @PostAuthorize, wenn die Entscheidung vom Rückgabewert abhängt.

Merksatz:

Bevorzuge Vorab-Prüfungen, außer die Entscheidung braucht den Rückgabewert.


33. @Secured

Eine weitere Method-Security-Annotation:


@Secured("ROLE_ADMIN")
public void deleteUser(Long id) {
}

Sie ist einfacher, aber weniger ausdrucksstark als @PreAuthorize.

@PreAuthorize kann nutzen:


roles
authorities
method parameters
authentication
custom beans
SpEL expressions

Merksatz:

@PreAuthorize ist ausdrucksstärker als @Secured.


34. JSR-250-Annotationen

Spring Security kann auch Annotationen wie diese unterstützen:


@RolesAllowed("ADMIN")
@PermitAll
@DenyAll

Aber dafür musst du JSR-250-Support aktivieren:


@EnableMethodSecurity(jsr250Enabled = true)

Für Spring-Security-Lernen konzentriere dich zuerst auf:


@PreAuthorize

Merksatz:

@PreAuthorize ist die flexibelste Method-Security-Annotation zum Einstieg.


35. Method Security funktioniert auf Spring Beans

Method Security gilt für von Spring verwaltete Beans.

Gut:


@Service
public class TaskService {

@PreAuthorize("hasAuthority('TASK_READ')")
public TaskDto findTask(Long id) {
...
}
}

Nicht sinnvoll bei Objekten, die keine Spring Beans sind.

Beispiel:


new TaskService()

Das umgeht Spring und seine Proxies.

Merksatz:

Method Security funktioniert über von Spring verwaltete Beans.


36. Self-Invocation-Falle

Ähnlich wie bei @Transactional ist Method Security oft proxy-basiert.

Problem:


@Service
public class TaskService {

public TaskDto outer(Long id) {
return inner(id);
}

@PreAuthorize("hasAuthority('TASK_READ')")
public TaskDto inner(Long id) {
return findTask(id);
}
}

Wenn outer() inner() innerhalb derselben Klasse aufruft, kann der Aufruf den Security-Proxy umgehen.

Besser:


put annotation on outer public method
move secured method to another Spring bean
avoid relying on internal calls for security

Merksatz:

Method Security kann durch Self-Invocation umgangen werden.


37. URL Security vs. Method Security

ThemaURL SecurityMethod Security
WoSecurityFilterChainService-/Controller-Methoden
StilDSL-KonfigurationAnnotationen
Am besten fürEndpunkt-RegelnBusiness-/Service-Regeln
Beispiel/api/admin/**@PreAuthorize("hasRole('ADMIN')")
Methodenargs nutzbarneinja
Datenebenebegrenztgut

Merksatz:

URL Security ist grob. Method Security ist fein.


38. URL Security und Method Security kombinieren

Gutes Design:


URL security:
- public endpoints
- admin area
- authenticated API
- HTTP method rules

Method security:
- service-level roles
- task ownership
- tenant access
- business action permission

Beispiel URL-Konfiguration:


.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)

Beispiel Service-Methode:


@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
public TaskDto findTask(Long taskId) {
...
}

Merksatz:

Nutze URL-Regeln für breiten Zugriff und Method Security für Geschäftszugriff.


39. Controller mit aktuellem Benutzer

Controller:


@GetMapping("/api/tasks/{taskId}")
public TaskDto getTask(
@PathVariable Long taskId,
@AuthenticationPrincipal AppUserPrincipal principal
) {
return taskService.findTask(taskId, principal);
}

Service:


@Transactional(readOnly = true)
public TaskDto findTask(Long taskId, AppUserPrincipal principal) {
TaskEntity task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

if (!task.getTenantId().equals(principal.getTenantId())) {
throw new AccessDeniedException("No access to this task");
}

return toDto(task);
}

Das ist explizit und leicht verständlich.

Merksatz:

Den aktuellen Benutzer an den Service zu übergeben macht Datenebenen-Prüfungen klar.


40. Alternative mit Method Security

Statt Principal zu übergeben und manuell zu prüfen:


@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
@Transactional(readOnly = true)
public TaskDto findTask(Long taskId) {
TaskEntity task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

return toDto(task);
}

Permission Service:


@Component("permissionService")
public class PermissionService {

private final TaskRepository taskRepository;

public PermissionService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}

public boolean canAccessTask(Long taskId, Authentication authentication) {
AppUserPrincipal principal = (AppUserPrincipal) authentication.getPrincipal();

return taskRepository.existsByIdAndTenantId(
taskId,
principal.getTenantId()
);
}
}

Repository:


boolean existsByIdAndTenantId(Long id, Long tenantId);

Das prüft den Zugriff, bevor vollständige Task-Details geladen werden.

Merksatz:

Berechtigungsprüfungen können effiziente Repository-Methoden wie existsBy... nutzen.


41. Vom Client gesendete User-IDs nicht vertrauen

Schlechte Anfrage:


POST /api/tasks
Content-Type: application/json

{
"title": "Secret task",
"userId": 123
}

Schlechter Service:


public TaskDto create(CreateTaskRequest request) {
UserEntity user = userRepository.findById(request.userId()).orElseThrow();
...
}

Problem:


client can pretend to be another user

Besser:


take current user from Authentication/SecurityContext
ignore userId from request for ownership

Gut:


public TaskDto create(CreateTaskRequest request, AppUserPrincipal principal) {
UserEntity user = userRepository.findById(principal.getId()).orElseThrow();
...
}

Merksatz:

Vertraue niemals einer vom Client gesendeten Benutzeridentität, wenn ein authentifizierter Benutzer verfügbar ist.


42. Nicht nur Frontend-Autorisierung nutzen

Schlechte Annahme:


The button is hidden in React, so backend is safe.

Falsch.

Ein Benutzer kann die API trotzdem direkt aufrufen mit:


Postman
curl
browser devtools
custom script

Das Backend muss Autorisierung erzwingen.

Merksatz:

Das Frontend versteckt Buttons. Das Backend erzwingt Sicherheit.


43. Autorisierung in Repository-Queries

Manchmal enthält die sicherste Query bereits den Zugriffsbereich.

Schlecht:


Optional<TaskEntity> findById(Long id);

Dann später Mandant prüfen.

Besser für Reads:


Optional<TaskEntity> findByIdAndTenantId(Long id, Long tenantId);

Service:


@Transactional(readOnly = true)
public TaskDto findTask(Long taskId, AppUserPrincipal principal) {
TaskEntity task = taskRepository.findByIdAndTenantId(
taskId,
principal.getTenantId()
).orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

return toDto(task);
}

Das vermeidet das Laden von Daten außerhalb des Mandanten des Benutzers.

Merksatz:

Scoping von Repository-Queries nach Mandant oder Owner ist sinnvoll, wenn möglich.


44. 404 vs. 403 bei Datenzugriff

Wenn ein Benutzer den Task eines anderen Mandanten anfordert: Soll die API 403 oder 404 zurückgeben?

Beides sind mögliche Designentscheidungen.

403

Bedeutet:


resource exists, but user is not allowed

404

Bedeutet:


resource not found from this user's perspective

Viele APIs liefern 404, um nicht preiszugeben, ob eine Ressource existiert.

Beispiel:


taskRepository.findByIdAndTenantId(taskId, principal.getTenantId())
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

Merksatz:

Bei mandantenübergreifenden Ressourcen kann 404 verhindern, dass Existenz durchsickert.


45. Rollen-Explosion

Schlechtes Design:


ROLE_ADMIN
ROLE_TASK_READER
ROLE_TASK_WRITER
ROLE_INVOICE_APPROVER
ROLE_CLIENT_EXPORTER
ROLE_PAYROLL_MANAGER
ROLE_DOCUMENT_UPLOADER

Wenn alles zur Rolle wird, werden Rollen unübersichtlich.

Besser:


roles = broad groups
authorities = specific permissions

Beispiel:


ROLE_ACCOUNTANT
TASK_READ
TASK_WRITE
INVOICE_APPROVE
DOCUMENT_UPLOAD

Merksatz:

Nutze Rollen für breite Gruppen und Berechtigungen für feingranulare Erlaubnisse.


46. Rollenhierarchie — Vorschau

Manchmal haben Rollen eine Hierarchie.

Beispiel:


ADMIN includes MANAGER permissions.
MANAGER includes USER permissions.

Konzept:


ROLE_ADMIN > ROLE_MANAGER > ROLE_USER

Spring Security unterstützt Rollenhierarchien, aber das ist ein fortgeschrittenes Thema.

Fürs Erste bleib einfach:


explicit roles and authorities

Merksatz:

Rollenhierarchien können wiederholte Regeln reduzieren, aber lerne zuerst die Grundlagen.


47. Security-Testing — Vorschau

URL-Security-Test:


@WithMockUser(roles = "ADMIN")
@Test
void adminCanAccessAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}

Method-Security-Test:


@WithMockUser(roles = "USER")
@Test
void userCannotDeleteUser() {
assertThatThrownBy(() -> userService.deleteUser(1L))
.isInstanceOf(AccessDeniedException.class);
}

Merksatz:

Security-Regeln solltest du mit erlaubten und verweigerten Benutzern testen.


48. Typische Prüfungsfallen

Falle 1

Authentifizierung und Autorisierung sind unterschiedlich.


Falle 2

401 und 403 sind unterschiedlich.


Falle 3

Rollen sind Berechtigungen mit dem Präfix ROLE_.


Falle 4

hasRole("ADMIN") prüft ROLE_ADMIN.


Falle 5

hasAuthority("ADMIN") prüft exakt ADMIN, nicht ROLE_ADMIN.


Falle 6

Nutze nicht hasRole("ROLE_ADMIN").


Falle 7

Die Reihenfolge der Regeln ist bei URL-Autorisierung wichtig.


Falle 8

Spezifische Request Matcher müssen vor anyRequest() stehen.


Falle 9

URL-Regeln sind grob.


Falle 10

Method Security braucht @EnableMethodSecurity.


Falle 11

@PreAuthorize prüft vor der Methodenausführung.


Falle 12

@PostAuthorize prüft nach der Methodenausführung.


Falle 13

Method Security gilt für Spring Beans.


Falle 14

Self-Invocation kann Method Security umgehen.


Falle 15

Verlasse dich nie nur auf Frontend-Autorisierung.


Falle 16

Vertraue keiner vom Client gesendeten User-ID für authentifizierte Aktionen.


Falle 17

Berechtigungen auf Datenebene gehören oft in Service-/Domain-Logik.


Falle 18

Repository-Queries können Mandanten-/Owner-Scope enthalten.


49. Echte Prüfungsfrage: Autorisierung

Frage:

Was ist Autorisierung?

Antwort:

Autorisierung ist der Prozess, zu entscheiden, worauf ein authentifizierter Benutzer zugreifen oder was er tun darf.


50. Echte Prüfungsfrage: Rolle

Frage:

Was ist eine Rolle in Spring Security?

Antwort:

Eine Rolle wird meist als Berechtigung mit dem Präfix ROLE_ dargestellt, zum Beispiel ROLE_ADMIN.


51. Echte Prüfungsfrage: Berechtigung

Frage:

Was ist eine Berechtigung (Authority)?

Antwort:

Eine Berechtigung ist eine dem Benutzer zugewiesene Erlaubnis, zum Beispiel ROLE_ADMIN, TASK_READ oder INVOICE_APPROVE.


52. Echte Prüfungsfrage: hasRole

Frage:

Was prüft hasRole("ADMIN")?

Antwort:

Es prüft, ob der aktuelle Benutzer die Berechtigung ROLE_ADMIN hat.


53. Echte Prüfungsfrage: hasAuthority

Frage:

Was prüft hasAuthority("TASK_READ")?

Antwort:

Es prüft, ob der aktuelle Benutzer exakt die Berechtigung TASK_READ hat.


54. Echte Prüfungsfrage: Regelreihenfolge

Frage:

Warum ist die Reihenfolge der Autorisierungsregeln wichtig?

Antwort:

Regeln werden der Reihe nach ausgewertet. Spezifische Regeln sollten vor allgemeinen Regeln wie anyRequest() stehen.


55. Echte Prüfungsfrage: Method Security

Frage:

Wie aktivierst du Method Security?

Antwort:

Füge @EnableMethodSecurity zu einer Konfigurationsklasse hinzu.


56. Echte Prüfungsfrage: @PreAuthorize

Frage:

Was macht @PreAuthorize?

Antwort:

@PreAuthorize prüft einen Autorisierungsausdruck, bevor die Methode aufgerufen wird. Wenn der Ausdruck fehlschlägt, läuft die Methode nicht.


57. Echte Prüfungsfrage: URL vs. Method Security

Frage:

Was ist der Unterschied zwischen URL Security und Method Security?

Antwort:

URL Security schützt Endpunkte auf HTTP-Request-Ebene. Method Security schützt Methodenaufrufe, meist in der Service-Schicht, und kann Methodenparameter sowie geschäftsspezifische Autorisierungsausdrücke nutzen.


58. Echte Prüfungsfrage: Berechtigung auf Datenebene

Frage:

Was ist eine Berechtigung auf Datenebene?

Antwort:

Berechtigung auf Datenebene bedeutet, dass der Zugriff von der konkreten Ressource oder den konkreten Daten abhängt — zum Beispiel, ob der aktuelle Benutzer demselben Mandanten wie der angeforderte Task angehört.


59. Interview-Antwort

Frage:

Erkläre Rollen und Berechtigungen in Spring Security.

Gute Antwort:

Spring Security arbeitet mit Berechtigungen. Eine Berechtigung ist eine Erlaubnis wie TASK_READ oder ROLE_ADMIN. Eine Rolle wird normalerweise als Berechtigung mit dem Präfix ROLE_ dargestellt. Zum Beispiel prüft hasRole("ADMIN") auf ROLE_ADMIN, während hasAuthority("TASK_READ") exakt TASK_READ prüft. Ich nutze Rollen für breite Gruppen und Berechtigungen für feingranulare Erlaubnisse.


60. Interview-Antwort

Frage:

Wie gestaltest du Autorisierung in einer Spring Boot REST API?

Gute Antwort:

Ich nutze URL-Regeln in der SecurityFilterChain für breiten Endpunktzugriff — öffentliche Endpunkte, Admin-Endpunkte und authentifizierte APIs. Dann erzwinge ich Business- und Datenebenen-Berechtigungen in der Service-Schicht, entweder mit expliziten Permission-Checks oder Method Security wie @PreAuthorize. Bei Multi-Tenant-Daten scope ich Repository-Queries nach Mandant oder Owner, damit Benutzer keine Daten außerhalb ihrer Berechtigungsgrenze sehen.


61. Interview-Antwort

Frage:

Was ist @PreAuthorize?

Gute Antwort:

@PreAuthorize ist eine Method-Security-Annotation, die einen Autorisierungsausdruck prüft, bevor eine Methode läuft. Sie kann Rollen, Berechtigungen, Methodenparameter, die aktuelle Authentifizierung prüfen oder eigene Permission-Beans aufrufen. Dafür muss Method Security mit @EnableMethodSecurity aktiviert sein.


62. Interview-Antwort

Frage:

Warum reichen URL-Regeln nicht aus?

Gute Antwort:

URL-Regeln eignen sich gut für breiten Zugriff, zum Beispiel Authentifizierung oder Admin-Rolle für einen Pfad. Aber viele echte Berechtigungen hängen von den konkreten Daten ab: Task-Ownership, Mandanten-Zugehörigkeit, Rechnungsstatus oder wer ein Dokument erstellt hat. Diese Prüfungen müssen in Service-/Domain-Logik oder Method Security passieren, nicht nur in der URL-Konfiguration.


63. Interview-Antwort

Frage:

Wie handhabst du Mandanten-Autorisierung?

Gute Antwort:

Ich nehme die Mandanten-Information des aktuellen Benutzers in den authentifizierten Principal auf oder lade sie aus der Datenbank. Dann prüfen Service-Methoden, ob die angeforderte Ressource zum selben Mandanten gehört. Wenn möglich, scope ich Repository-Queries direkt, zum Beispiel findByIdAndTenantId, damit Daten außerhalb des Mandanten gar nicht erst geladen werden. Bei mandantenübergreifenden Zugriffsversuchen kann die API je nach Design 403 oder 404 zurückgeben.


64. Kleine Code-Übung

Security-Konfiguration:


@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")
.requestMatchers(HttpMethod.GET, "/api/tasks/**").hasAuthority("TASK_READ")
.requestMatchers(HttpMethod.POST, "/api/tasks/**").hasAuthority("TASK_WRITE")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}

Fragen:

  1. Wer darf /api/auth/login aufrufen?
  2. Wer darf /api/admin/users aufrufen?
  3. Welche Berechtigung braucht GET /api/tasks?
  4. Welche Berechtigung braucht POST /api/tasks?
  5. Was passiert mit allen anderen Requests?

Antworten:

  1. Jeder.
  2. Benutzer mit Rolle ADMIN, also Berechtigung ROLE_ADMIN.
  3. TASK_READ.
  4. TASK_WRITE.
  5. Sie erfordern Authentifizierung.

65. Kleine Bug-Übung 1

Problem:


.requestMatchers("/api/admin/**").hasRole("ROLE_ADMIN")

Frage:

Was ist falsch?

Antwort:

hasRole fügt das Präfix ROLE_ automatisch hinzu. Nutze:


hasRole("ADMIN")

oder:


hasAuthority("ROLE_ADMIN")

66. Kleine Bug-Übung 2

Problem:


.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
.requestMatchers("/api/auth/**").permitAll()
)

Frage:

Was ist falsch?

Antwort:

anyRequest() ist eine allgemeine Catch-all-Regel. Sie muss nach spezifischen Regeln kommen.

Richtig:


.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)

67. Kleine Bug-Übung 3

Problem:


@PostMapping("/api/tasks")
public TaskDto create(@RequestBody CreateTaskRequest request) {
return taskService.create(request.userId(), request.title());
}

Frage:

Warum ist das gefährlich?

Antwort:

Der Client sendet userId — er kann sich als anderer Benutzer ausgeben. Nutze den authentifizierten Principal von Spring Security statt einer Benutzeridentität aus dem Request-Body.


68. Kleine Bug-Übung 4

Problem:


@Service
public class TaskService {

public TaskDto findTask(Long id) {
return securedFindTask(id);
}

@PreAuthorize("hasAuthority('TASK_READ')")
public TaskDto securedFindTask(Long id) {
return ...
}
}

Frage:

Was ist die Falle?

Antwort:

Das ist Self-Invocation. Eine Methode in derselben Klasse ruft die gesicherte Methode direkt auf und umgeht dabei möglicherweise den Spring-Security-Proxy. Setze @PreAuthorize auf die von außen aufgerufene öffentliche Methode oder verschiebe die gesicherte Methode in eine andere Spring Bean.


Übungsfragen und Antworten

Frage 1

Was ist Autorisierung?

Antwort:

Autorisierung ist der Prozess, zu entscheiden, worauf ein authentifizierter Benutzer zugreifen oder was er tun darf.


Frage 2

Was ist der Unterschied zwischen Authentifizierung und Autorisierung?

Antwort:

Authentifizierung beweist, wer der Benutzer ist. Autorisierung prüft, was der Benutzer tun darf.


Frage 3

Was ist eine Berechtigung?

Antwort:

Eine Berechtigung ist eine dem Benutzer zugewiesene Erlaubnis, zum Beispiel ROLE_ADMIN, TASK_READ oder INVOICE_APPROVE.


Frage 4

Was ist eine Rolle?

Antwort:

Eine Rolle ist meist eine breite Gruppe von Berechtigungen und wird üblicherweise als Berechtigung mit dem Präfix ROLE_ dargestellt.


Frage 5

Was prüft hasRole("ADMIN")?

Antwort:

Es prüft, ob der Benutzer die Berechtigung ROLE_ADMIN hat.


Frage 6

Was prüft hasAuthority("TASK_READ")?

Antwort:

Es prüft, ob der Benutzer exakt die Berechtigung TASK_READ hat.


Frage 7

Warum ist hasRole("ROLE_ADMIN") meist falsch?

Antwort:

Weil hasRole das Präfix ROLE_ automatisch hinzufügt. hasRole("ROLE_ADMIN") sucht effektiv nach ROLE_ROLE_ADMIN.


Frage 8

Warum ist die Reihenfolge der URL-Regeln wichtig?

Antwort:

Regeln werden der Reihe nach ausgewertet. Spezifische Regeln müssen vor allgemeinen Catch-all-Regeln wie anyRequest() stehen.


Frage 9

Was bedeutet permitAll()?

Antwort:

permitAll() bedeutet, dass jeder zugreifen darf — auch nicht authentifizierte Benutzer.


Frage 10

Was bedeutet authenticated()?

Antwort:

authenticated() bedeutet, dass der Benutzer angemeldet oder anderweitig authentifiziert sein muss.


Frage 11

Was ist Method Security?

Antwort:

Method Security bedeutet, Autorisierungsregeln auf Methodenaufrufe anzuwenden, oft auf Service-Methoden.


Frage 12

Wie aktivierst du Method Security?

Antwort:

Nutze @EnableMethodSecurity auf einer Konfigurationsklasse.


Frage 13

Was macht @PreAuthorize?

Antwort:

@PreAuthorize prüft einen Autorisierungsausdruck, bevor die Methode läuft.


Frage 14

Was macht @PostAuthorize?

Antwort:

@PostAuthorize prüft die Autorisierung nach dem Rückgabewert der Methode, oft mit dem zurückgegebenen Objekt.


Frage 15

Wie kann @PreAuthorize Methodenparameter nutzen?

Antwort:

Nutze #parameterName im Ausdruck, zum Beispiel @PreAuthorize("#userId == authentication.principal.id").


Frage 16

Wie kann @PreAuthorize einen eigenen Permission Service aufrufen?

Antwort:

Referenziere die Bean per Name, zum Beispiel @PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)").


Frage 17

Was ist eine Berechtigung auf Datenebene?

Antwort:

Berechtigung auf Datenebene bedeutet, dass der Zugriff von der konkreten Ressource oder den konkreten Daten abhängt — zum Beispiel Ownership oder Mandanten-Zugehörigkeit.


Frage 18

Warum reichen URL-Regeln für Mandanten-Sicherheit nicht aus?

Antwort:

URL-Regeln können breiten Endpunktzugriff prüfen, wissen aber meist nicht, ob der angeforderte Client, Task, die Rechnung oder das Dokument zum Mandanten des aktuellen Benutzers gehört.


Frage 19

Warum solltest du vom Client gesendeten User-IDs nicht vertrauen?

Antwort:

Weil der Client Request-Daten manipulieren und sich als anderer Benutzer ausgeben kann. Nutze stattdessen den authentifizierten Principal.


Frage 20

Was ist die Self-Invocation-Falle bei Method Security?

Antwort:

Self-Invocation passiert, wenn eine Methode in derselben Klasse eine andere gesicherte Methode direkt aufruft. Der Aufruf kann den Spring-Proxy umgehen, sodass Method Security möglicherweise nicht greift.

Merksätze zum Mitnehmen

  • Autorisierung prüft Berechtigungen.
  • Authentifizierung beweist Identität.
  • Berechtigungen sind Erlaubnisse.
  • Rollen sind Berechtigungen mit dem Präfix ROLE_.
  • hasRole("ADMIN") prüft ROLE_ADMIN.
  • hasAuthority("TASK_READ") prüft exakt TASK_READ.
  • Nutze nicht hasRole("ROLE_ADMIN").
  • URL-Autorisierung schützt Endpunkte.
  • Die Reihenfolge der Regeln ist wichtig.
  • Spezifische Regeln stehen vor anyRequest().
  • URL Security ist grob.
  • Method Security ist fein.
  • Aktiviere Method Security mit @EnableMethodSecurity.
  • @PreAuthorize prüft vor der Methodenausführung.
  • @PostAuthorize prüft nach der Methodenausführung.
  • @PreAuthorize kann Methodenparameter nutzen.
  • @PreAuthorize kann eigene Permission-Beans aufrufen.
  • Berechtigungen auf Datenebene gehören in Service-/Domain-Logik.
  • Mandanten-Sicherheit muss Mandanten-Zugehörigkeit prüfen.
  • Vertraue keiner vom Client gesendeten Benutzeridentität.
  • Das Backend muss Autorisierung erzwingen, auch wenn das Frontend Buttons versteckt.
  • Repository-Queries können Mandanten- oder Owner-Scope enthalten.
  • Method Security funktioniert auf von Spring verwalteten Beans.
  • Self-Invocation kann Method Security umgehen.