Zum Hauptinhalt springen

Woche 6, Tag 5 — Security Testing

Ziel

Heute lernst du, wie du Spring Security testest.

Die Kernfragen:

  1. Warum sind Sicherheitstests wichtig?
  2. Welche Abhängigkeit brauchst du?
  3. Wie testest du gesicherte Controller mit MockMvc?
  4. Was ist @WithMockUser?
  5. Wie testest du Rollen und Authorities?
  6. Wie testest du 401 und 403?
  7. Wie testest du CSRF?
  8. Wie testest du HTTP Basic?
  9. Wie testest du Methodensicherheit?
  10. Wie testest du @PreAuthorize?
  11. Wie testest du benutzerdefinierte Principal-Logik?
  12. Was sind häufige Fehler bei Sicherheitstests?

1. Kurz-Wiederholung aus Woche 6, Tag 4

In Tag 4 hast du gelernt:

  • CSRF bedeutet Cross-Site Request Forgery.
  • CSRF ist besonders bei Browser-Cookies und Sessions wichtig.
  • Stateless Bearer-Token-APIs deaktivieren CSRF oft, aber nicht blind.
  • CORS steuert, welche Browser-Origins die API aufrufen dürfen.
  • Session-Sicherheit speichert den Authentifizierungsstatus auf dem Server.
  • Stateless-Sicherheit authentifiziert jede Anfrage unabhängig.
  • SessionCreationPolicy.STATELESS bedeutet: keine HTTP-Session für Auth-Status.
  • JWT hat Header, Payload und Signatur.
  • JWT ist signiert, nicht zwingend verschlüsselt.
  • Bearer Token bedeutet: Besitz gewährt Zugriff.
  • Öffentliche Endpunkte sollten explizit sein.
  • Alles andere sollte normalerweise authentifiziert sein.

Merksatz:


Öffentlich als Ausnahme, standardmäßig geschützt.

Heute lernst du, wie du Sicherheitsregeln mit Tests belegen kannst.


2. Warum Sicherheitstests wichtig sind

Sicherheitsfehler sind gefährlich.

Beispiele:


öffentlicher Endpunkt versehentlich geschützt
geschützter Endpunkt versehentlich öffentlich
USER kann ADMIN-Endpunkt aufrufen
fehlendes CSRF-Token wird trotzdem akzeptiert
POST-Anfrage schlägt in Tests mit 403 fehl
Methodensicherheit nicht aktiviert
@PreAuthorize nicht getestet
JWT-Authorities falsch gemappt
benutzerdefinierter Principal fehlt im Test

Sicherheitstests helfen zu prüfen:


401 wenn nicht authentifiziert
403 wenn authentifiziert, aber nicht berechtigt
200 wenn erlaubt
CSRF-Verhalten
Rollen und Authorities
Methodensicherheit
benutzerdefinierte Autorisierungslogik

Merksatz:

Sicherheitstests beweisen, dass erlaubte Benutzer zugelassen und verbotene Benutzer blockiert werden.


3. Abhängigkeit für Sicherheitstests

Füge diese Test-Abhängigkeit hinzu:


testImplementation("org.springframework.security:spring-security-test")

In Maven:


<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

Das liefert nützliche Test-Helfer:


@WithMockUser
@WithAnonymousUser
@WithUserDetails
SecurityMockMvcRequestPostProcessors
csrf()
httpBasic()
user()
jwt()
oauth2Login()

Merksatz:

spring-security-test liefert Test-Helfer für Spring Security.


4. Arten von Sicherheitstests

Gängige Sicherheitstest-Typen:


Controller-Sicherheitstests
URL-Autorisierungstests
CSRF-Tests
Methodensicherheitstests
JWT-/Resource-Server-Tests
benutzerdefinierte Berechtigungstests
Integrationstests

Beispielfragen:


Kann ein anonymer Benutzer einen öffentlichen Endpunkt aufrufen?
Kann ein anonymer Benutzer einen geschützten Endpunkt aufrufen?
Kann USER einen User-Endpunkt aufrufen?
Kann USER einen Admin-Endpunkt aufrufen?
Kann ADMIN einen Admin-Endpunkt aufrufen?
Schlägt POST ohne CSRF fehl, wenn CSRF aktiviert ist?
Blockiert @PreAuthorize einen nicht autorisierten Service-Aufruf?

Merksatz:

Teste sowohl erfolgreichen Zugriff als auch verweigerten Zugriff.


5. MockMvc-Sicherheitstests

Mit MockMvc testest du MVC-Controller, ohne einen echten Server zu starten.

Beispiel:


@SpringBootTest
@AutoConfigureMockMvc
class TaskControllerSecurityTest {

@Autowired
private MockMvc mockMvc;
}

Mit Spring Boot und @AutoConfigureMockMvc sind Spring-Security-Filter normalerweise enthalten.

Dann kannst du HTTP-Anfragen testen:


mockMvc.perform(get("/api/tasks"))
.andExpect(status().isUnauthorized());

Merksatz:

MockMvc kann gesicherte Endpunkte über die Spring-Security-Filterkette testen.


6. MockMvc mit manuellem Setup

Manchmal konfigurierst du MockMvc manuell.

Dann brauchst du:


.apply(springSecurity())

Beispiel:


@BeforeEach
void setup(WebApplicationContext context) {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}

Das integriert Spring Security mit MockMvc.

Merksatz:

Wenn MockMvc manuell gebaut wird, wende springSecurity() an.


7. Beispiel-Controller für Tests

Controller:


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<String> list() {
return List.of("Task A", "Task B");
}

@PostMapping
public ResponseEntity<String> create(@RequestBody String body) {
return ResponseEntity.status(HttpStatus.CREATED).body("created");
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return ResponseEntity.noContent().build();
}
}

Security-Konfiguration:


@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/tasks/**").hasAuthority("TASK_READ")
.requestMatchers(HttpMethod.POST, "/api/tasks/**").hasAuthority("TASK_WRITE")
.requestMatchers(HttpMethod.DELETE, "/api/tasks/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}

Regeln:


GET /api/tasks braucht TASK_READ
POST /api/tasks braucht TASK_WRITE
DELETE /api/tasks/{id} braucht ADMIN-Rolle
alles andere braucht Authentifizierung

8. @WithMockUser

@WithMockUser führt einen Test als simulierten authentifizierten Benutzer aus.

Beispiel:


@Test
@WithMockUser
void authenticatedUserCanCallEndpoint() throws Exception {
mockMvc.perform(get("/api/profile"))
.andExpect(status().isOk());
}

Standard-Mock-Benutzer:


username = user
password = password
role = USER
authority = ROLE_USER

Merksatz:

@WithMockUser erstellt einen authentifizierten Benutzer für den Test.


9. @WithMockUser mit Rollen

Beispiel:


@Test
@WithMockUser(username = "admin@example.com", roles = "ADMIN")
void adminCanDeleteTask() throws Exception {
mockMvc.perform(delete("/api/tasks/1")
.with(csrf()))
.andExpect(status().isNoContent());
}

Wichtig:


roles = "ADMIN"

erzeugt die Authority:


ROLE_ADMIN

Merksatz:

@WithMockUser(roles = "ADMIN") verleiht die Authority ROLE_ADMIN.


10. @WithMockUser mit Authorities

Beispiel:


@Test
@WithMockUser(authorities = "TASK_READ")
void userWithTaskReadCanListTasks() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isOk());
}

Das verleiht exakt die Authority:


TASK_READ

Merksatz:

Verwende authorities, wenn du feingranulare Berechtigungen testest.


11. Rollen vs. Authorities in Tests

Rollen-Test:


@WithMockUser(roles = "ADMIN")

erzeugt:


ROLE_ADMIN

Authority-Test:


@WithMockUser(authorities = "ROLE_ADMIN")

erzeugt exakt:


ROLE_ADMIN

Authority-Test:


@WithMockUser(authorities = "TASK_READ")

erzeugt exakt:


TASK_READ

Häufiger Fehler:


@WithMockUser(roles = "ROLE_ADMIN")

Falsch, weil roles automatisch ROLE_ hinzufügt.

Merksatz:

In Tests fügen roles ROLE_ hinzu; authorities sind exakt.


12. 401 Unauthorized testen

401 bedeutet:


nicht authentifiziert

Test:


@Test
void anonymousCannotAccessProtectedEndpoint() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isUnauthorized());
}

Kein @WithMockUser.

Keine Authentifizierung.

Erwartetes Ergebnis:


401 Unauthorized

Merksatz:

Um 401 zu testen, sende die Anfrage ohne Authentifizierung.


13. 403 Forbidden testen

403 bedeutet:


authentifiziert, aber nicht berechtigt

Beispiel:


@Test
@WithMockUser(roles = "USER")
void normalUserCannotDeleteTask() throws Exception {
mockMvc.perform(delete("/api/tasks/1")
.with(csrf()))
.andExpect(status().isForbidden());
}

Warum?


Benutzer ist authentifiziert
aber DELETE erfordert ADMIN

Merksatz:

Um 403 zu testen, authentifiziere dich als Benutzer ohne ausreichende Berechtigung.


14. 200 OK testen

Beispiel:


@Test
@WithMockUser(authorities = "TASK_READ")
void userWithTaskReadCanListTasks() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isOk());
}

Warum?


GET /api/tasks erfordert TASK_READ
Mock-Benutzer hat TASK_READ

Merksatz:

Ein guter Sicherheitstest enthält auch einen Erfolgsfall.


15. 201 Created mit CSRF testen

Wenn CSRF aktiviert ist, brauchen unsichere HTTP-Methoden ein CSRF-Token.

Unsichere Methoden:


POST
PUT
PATCH
DELETE

Test:


@Test
@WithMockUser(authorities = "TASK_WRITE")
void userWithTaskWriteCanCreateTask() throws Exception {
mockMvc.perform(post("/api/tasks")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Learn Security Testing"}
"""))
.andExpect(status().isCreated());
}

Merksatz:

Wenn CSRF aktiviert ist, brauchen POST-Tests .with(csrf()).


16. Fehlendes CSRF testen

Beispiel:


@Test
@WithMockUser(authorities = "TASK_WRITE")
void postWithoutCsrfIsForbiddenWhenCsrfEnabled() throws Exception {
mockMvc.perform(post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Learn Security Testing"}
"""))
.andExpect(status().isForbidden());
}

Erwartetes Ergebnis:


403 Forbidden

Warum?


Benutzer ist authentifiziert
Benutzer hat die Authority
aber CSRF-Token fehlt

Merksatz:

Ein fehlendes CSRF-Token kann 403 verursachen, auch wenn der Benutzer berechtigt ist.


17. Ungültiges CSRF testen

Beispiel:


@Test
@WithMockUser(authorities = "TASK_WRITE")
void postWithInvalidCsrfIsForbidden() throws Exception {
mockMvc.perform(post("/api/tasks")
.with(csrf().useInvalidToken())
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Invalid CSRF"}
"""))
.andExpect(status().isForbidden());
}

Merksatz:

Tests können auch ungültige CSRF-Token prüfen.


18. Wenn CSRF deaktiviert ist

Wenn die Security-Konfiguration Folgendes hat:


.csrf(csrf -> csrf.disable())

dann brauchen POST-Tests kein:


.with(csrf())

Beispiel:


@Test
@WithMockUser(authorities = "TASK_WRITE")
void postWorksWithoutCsrfWhenCsrfDisabled() throws Exception {
mockMvc.perform(post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Stateless API"}
"""))
.andExpect(status().isCreated());
}

Merksatz:

Ob Tests CSRF brauchen, hängt von der Security-Konfiguration ab.


19. HTTP Basic testen

Wenn die Konfiguration HTTP Basic aktiviert:


.httpBasic(Customizer.withDefaults())

kannst du testen mit:


import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;

Beispiel:


@Test
void validBasicAuthCanAccessEndpoint() throws Exception {
mockMvc.perform(get("/api/tasks")
.with(httpBasic("user@example.com", "password")))
.andExpect(status().isOk());
}

Das testet echte Benutzername/Passwort-Authentifizierung, wenn Benutzer konfiguriert sind.

Merksatz:

httpBasic() testet Basic-Authentifizierung über Spring Security.


20. @WithMockUser vs. httpBasic()

@WithMockUser:


setzt einen simulierten authentifizierten Benutzer in den Security Context
testet keine Passwortprüfung
schnell und einfach
gut für Autorisierungstests

httpBasic():


sendet Benutzername/Passwort
testet den Authentifizierungsfluss
nutzt UserDetailsService und PasswordEncoder
besser für Login-/Authentifizierungs-Integrationstests

Merksatz:

Nutze @WithMockUser für Autorisierung, httpBasic() für den Authentifizierungsfluss.


21. Öffentliche Endpunkte testen

Öffentlicher Endpunkt:


.requestMatchers("/api/auth/**").permitAll()

Test:


@Test
void anonymousCanAccessLoginEndpoint() throws Exception {
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"email":"user@example.com","password":"password"}
"""))
.andExpect(status().isOk());
}

Wenn CSRF aktiviert ist, braucht dieser POST eventuell noch:


.with(csrf())

es sei denn, CSRF ist für diesen Endpunkt deaktiviert oder ignoriert.

Merksatz:

permitAll deaktiviert CSRF nicht automatisch.


22. Prüfungsfalle: permitAll vs. CSRF

Konfiguration:


.requestMatchers("/api/auth/**").permitAll()

Das bedeutet:


keine Authentifizierung erforderlich

Aber wenn CSRF aktiviert ist:


POST /api/auth/login braucht eventuell trotzdem ein CSRF-Token

Merksatz:

permitAll umgeht die Authentifizierung, nicht zwingend CSRF.


23. Anonymen Benutzer explizit testen

Du kannst verwenden:


@WithAnonymousUser

Beispiel:


@Test
@WithAnonymousUser
void anonymousCannotAccessTasks() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isUnauthorized());
}

Das macht den anonymen Fall explizit.

Merksatz:

@WithAnonymousUser macht anonyme Tests klar erkennbar.


24. Controller mit aktuellem Benutzer testen

Controller:


@GetMapping("/api/me")
public MeDto me(Authentication authentication) {
return new MeDto(authentication.getName());
}

Test:


@Test
@WithMockUser(username = "steve@example.com")
void returnsCurrentUser() throws Exception {
mockMvc.perform(get("/api/me"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("steve@example.com"));
}

DTO:


public record MeDto(String email) {
}

Merksatz:

@WithMockUser(username = "...") steuert authentication.getName().


25. Authorities im Controller testen

Controller:


@GetMapping("/api/me/authorities")
public List<String> authorities(Authentication authentication) {
return authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.toList();
}

Test:


@Test
@WithMockUser(authorities = {"TASK_READ", "TASK_WRITE"})
void returnsAuthorities() throws Exception {
mockMvc.perform(get("/api/me/authorities"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0]").value("TASK_READ"))
.andExpect(jsonPath("$[1]").value("TASK_WRITE"));
}

Merksatz:

@WithMockUser(authorities = ...) steuert exakte Authorities.


26. Methodensicherheit testen

Methodensicherheit schützt Service-Methoden.

Beispiel-Service:


@Service
public class AdminService {

@PreAuthorize("hasRole('ADMIN')")
public String adminOnly() {
return "secret";
}
}

Methodensicherheit aktivieren:


@Configuration
@EnableMethodSecurity
public class SecurityConfig {
}

Test:


@SpringBootTest
class AdminServiceSecurityTest {

@Autowired
private AdminService adminService;

@Test
@WithMockUser(roles = "ADMIN")
void adminCanCallMethod() {
assertThat(adminService.adminOnly()).isEqualTo("secret");
}

@Test
@WithMockUser(roles = "USER")
void userCannotCallMethod() {
assertThatThrownBy(() -> adminService.adminOnly())
.isInstanceOf(AccessDeniedException.class);
}
}

Merksatz:

Methodensicherheitstests rufen die Service-Methode auf und erwarten Erfolg oder AccessDeniedException.


27. @PreAuthorize mit Authorities testen

Service:


@Service
public class TaskService {

@PreAuthorize("hasAuthority('TASK_DELETE')")
public void deleteTask(Long taskId) {
// delete task
}
}

Test — erlaubt:


@Test
@WithMockUser(authorities = "TASK_DELETE")
void userWithAuthorityCanDeleteTask() {
taskService.deleteTask(1L);
}

Test — verweigert:


@Test
@WithMockUser(authorities = "TASK_READ")
void userWithoutAuthorityCannotDeleteTask() {
assertThatThrownBy(() -> taskService.deleteTask(1L))
.isInstanceOf(AccessDeniedException.class);
}

Merksatz:

Teste sowohl passende als auch nicht passende Authorities.


28. @PreAuthorize mit Methodenparametern testen

Service:


@Service
public class UserProfileService {

@PreAuthorize("#userId == authentication.principal.id")
public String getProfile(Long userId) {
return "profile";
}
}

Das braucht einen benutzerdefinierten Principal mit id.

@WithMockUser erstellt einen normalen Spring-Security-Benutzer, nicht deinen benutzerdefinierten Principal.

Deshalb kann dieser Test fehlschlagen:


@WithMockUser

weil:


authentication.principal.id existiert nicht

Merksatz:

@WithMockUser reicht nicht, wenn Ausdrücke benutzerdefinierte Principal-Felder brauchen.


29. Benutzerdefinierten Principal mit Request PostProcessor testen

Für Controller-Tests kannst du verwenden:


.with(user(customPrincipal))

Benutzerdefinierter Principal:


AppUserPrincipal principal = new AppUserPrincipal(
10L,
5L,
"steve@example.com",
List.of(new SimpleGrantedAuthority("ROLE_USER")),
true
);

Test:


@Test
void currentUserCanAccessOwnTenant() throws Exception {
AppUserPrincipal principal = new AppUserPrincipal(
10L,
5L,
"steve@example.com",
List.of(new SimpleGrantedAuthority("ROLE_USER")),
true
);

mockMvc.perform(get("/api/tenants/5/tasks")
.with(user(principal)))
.andExpect(status().isOk());
}

Merksatz:

Nutze .with(user(customPrincipal)), wenn der Controller einen benutzerdefinierten Principal braucht.


30. Benutzerdefinierten Principal mit @WithSecurityContext testen

Für Methodensicherheitstests mit benutzerdefiniertem Principal kannst du eine eigene Annotation mit erstellen:


@WithSecurityContext

Beispiel-Annotation:


@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockAppUserSecurityContextFactory.class)
public @interface WithMockAppUser {

long id() default 1L;

long tenantId() default 1L;

String email() default "user@example.com";

String[] authorities() default {"ROLE_USER"};
}

Factory:


public class WithMockAppUserSecurityContextFactory
implements WithSecurityContextFactory<WithMockAppUser> {

@Override
public SecurityContext createSecurityContext(WithMockAppUser annotation) {
List<GrantedAuthority> authorities = Arrays.stream(annotation.authorities())
.map(SimpleGrantedAuthority::new)
.toList();

AppUserPrincipal principal = new AppUserPrincipal(
annotation.id(),
annotation.tenantId(),
annotation.email(),
authorities,
true
);

Authentication authentication =
new UsernamePasswordAuthenticationToken(
principal,
"password",
authorities
);

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);

return context;
}
}

Verwendung:


@Test
@WithMockAppUser(id = 10L, tenantId = 5L, authorities = {"TASK_READ"})
void canAccessOwnTenantData() {
taskService.findTenantTasks(5L);
}

Merksatz:

Nutze ein eigenes @WithSecurityContext für Methodensicherheitstests mit benutzerdefiniertem Principal.


31. Benutzerdefinierten Permission-Service testen

Permission-Service:


@Component("permissionService")
public class PermissionService {

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

return principal.getTenantId().equals(5L);
}
}

Service:


@PreAuthorize("@permissionService.canAccessTask(#taskId, authentication)")
public TaskDto findTask(Long taskId) {
return new TaskDto(taskId, "Test task");
}

Test:


@Test
@WithMockAppUser(tenantId = 5L)
void userWithPermissionCanAccessTask() {
TaskDto result = taskService.findTask(1L);

assertThat(result.id()).isEqualTo(1L);
}

Test — verweigert:


@Test
@WithMockAppUser(tenantId = 99L)
void userWithoutPermissionCannotAccessTask() {
assertThatThrownBy(() -> taskService.findTask(1L))
.isInstanceOf(AccessDeniedException.class);
}

Merksatz:

Benutzerdefinierte Berechtigungslogik braucht erlaubte und verweigerte Tests.


32. JWT Resource Server mit MockMvc testen

Bei JWT-basierten APIs kann Spring Security Test-Support ein JWT simulieren.

Beispiel:


import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;

Test:


@Test
void userWithJwtCanReadTasks() throws Exception {
mockMvc.perform(get("/api/tasks")
.with(jwt().authorities(
new SimpleGrantedAuthority("SCOPE_task:read")
)))
.andExpect(status().isOk());
}

Das braucht keinen echten Token.

Es erstellt eine simulierte JWT-Authentifizierung für den Test.

Merksatz:

Nutze jwt(), um Resource-Server-Autorisierung zu testen, ohne ein echtes JWT zu erzeugen.


33. JWT Claims testen

Beispiel:


@Test
void jwtWithTenantClaimCanAccessTenantEndpoint() throws Exception {
mockMvc.perform(get("/api/tenants/5/tasks")
.with(jwt()
.jwt(jwt -> jwt
.subject("steve@example.com")
.claim("tenantId", 5L)
)
.authorities(new SimpleGrantedAuthority("SCOPE_task:read"))
))
.andExpect(status().isOk());
}

Nützlich, wenn Controller oder Converter JWT Claims nutzen.

Merksatz:

JWT-Tests können Claims und Authorities simulieren.


34. Verweigerte JWT-Authority testen

Endpunkt erfordert:


.hasAuthority("SCOPE_task:write")

Test:


@Test
void jwtWithoutWriteScopeCannotCreateTask() throws Exception {
mockMvc.perform(post("/api/tasks")
.with(jwt().authorities(
new SimpleGrantedAuthority("SCOPE_task:read")
))
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"New task"}
"""))
.andExpect(status().isForbidden());
}

Wenn CSRF aktiviert ist, füge hinzu:


.with(csrf())

Wenn stateless JWT-Konfiguration CSRF deaktiviert, brauchst du kein CSRF.

Merksatz:

JWT-Authority-Tests sollten erlaubte und verbotene Scopes prüfen.


35. Login-Endpunkt testen

Login-Controller nutzt:


AuthenticationManager

Test mit echt konfiguriertem Benutzer:


@Test
void loginWithValidCredentialsReturnsToken() throws Exception {
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"email": "user@example.com",
"password": "password"
}
"""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.token").exists());
}

Falsches Passwort:


@Test
void loginWithBadPasswordReturnsUnauthorized() throws Exception {
mockMvc.perform(post("/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"email": "user@example.com",
"password": "wrong"
}
"""))
.andExpect(status().isUnauthorized());
}

Wenn CSRF aktiviert ist, braucht der Login-POST eventuell .with(csrf()).

Merksatz:

Login-Tests sollten gültige und ungültige Credentials prüfen.


36. Testen mit @WebMvcTest

@WebMvcTest lädt nur MVC-bezogene Komponenten.

Beispiel:


@WebMvcTest(TaskController.class)
class TaskControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private TaskService taskService;
}

Mit aktivierter Security kann @WebMvcTest trotzdem Security-Filter anwenden.

Wenn der Controller von Security-Konfiguration oder benutzerdefinierten Beans abhängt, brauchst du eventuell:


@Import(SecurityConfig.class)

oder musst fehlende Beans mocken.

Merksatz:

@WebMvcTest ist ein Slice — importiere oder mocke Security-Abhängigkeiten nach Bedarf.


37. Testen mit @SpringBootTest

@SpringBootTest lädt den vollständigen Application Context.

Beispiel:


@SpringBootTest
@AutoConfigureMockMvc
class SecurityIntegrationTest {

@Autowired
private MockMvc mockMvc;
}

Gut für:


vollständige Security-Konfiguration
echte Filter
Methodensicherheit
Repository-/Service-Integration
Login-Flow
JWT-/Resource-Server-Konfiguration

Langsamer als @WebMvcTest.

Merksatz:

Nutze @SpringBootTest für vollständige Security-Integrationstests.


38. Testtyp wählen

TesttypGut für
@WebMvcTestController + URL-Security-Slice
@SpringBootTestvollständige Security-Integration
Service-Test mit @WithMockUserMethodensicherheit
MockMvc mit jwt()Resource-Server-Autorisierung
MockMvc mit httpBasic()Basic-Authentifizierungsfluss
eigenes @WithSecurityContextbenutzerdefinierte Principal-Tests

Merksatz:

Wähle den kleinsten Test, der die Sicherheitsregel belegt.


39. @AuthenticationPrincipal testen

Controller:


@GetMapping("/api/me")
public MeDto me(@AuthenticationPrincipal AppUserPrincipal principal) {
return new MeDto(principal.getId(), principal.getUsername());
}

Test:


@Test
void returnsCustomPrincipalData() throws Exception {
AppUserPrincipal principal = new AppUserPrincipal(
10L,
5L,
"steve@example.com",
List.of(new SimpleGrantedAuthority("ROLE_USER")),
true
);

mockMvc.perform(get("/api/me")
.with(user(principal)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(10))
.andExpect(jsonPath("$.email").value("steve@example.com"));
}

Merksatz:

Für @AuthenticationPrincipal teste mit dem gleichen Principal-Typ, den der Controller erwartet.


40. Aktuellen Benutzer testen, der an den Service übergeben wird

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(@AuthenticationPrincipal AppUserPrincipal principal) {
return taskService.listForTenant(principal.getTenantId());
}

Test:


@Test
void passesTenantIdToService() throws Exception {
AppUserPrincipal principal = new AppUserPrincipal(
10L,
5L,
"steve@example.com",
List.of(new SimpleGrantedAuthority("ROLE_USER")),
true
);

when(taskService.listForTenant(5L))
.thenReturn(List.of(new TaskDto(1L, "Task A")));

mockMvc.perform(get("/api/tasks")
.with(user(principal)))
.andExpect(status().isOk());

verify(taskService).listForTenant(5L);
}

Merksatz:

Controller-Sicherheitstests können prüfen, ob aktuelle Benutzerdaten korrekt weitergegeben werden.


41. Häufiger Fehler: Test besteht ohne Security

Fehler:


mockMvc = MockMvcBuilders.standaloneSetup(controller).build();

Das enthält eventuell keine Spring-Security-Filter.

Ein Test kann bestehen, obwohl die echte App die Anfrage blockieren würde.

Fix:


mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();

oder nutze:


@SpringBootTest
@AutoConfigureMockMvc

Merksatz:

Stelle sicher, dass Security-Filter in Sicherheitstests wirklich aktiv sind.


42. Häufiger Fehler: 401 erwartet, aber 403 erhalten

Mögliche Ursachen:


CSRF-Token fehlt
Benutzer ist authentifiziert, aber nicht berechtigt
anonyme Behandlung unterscheidet sich je nach Konfiguration
Anfrage hat ungültige Auth

Beispiel:


@Test
@WithMockUser
void postFails() throws Exception {
mockMvc.perform(post("/api/tasks"))
.andExpect(status().isUnauthorized());
}

Tatsächliches Ergebnis:


403

Warum?


@WithMockUser bedeutet authentifiziert
POST fehlt eventuell CSRF
oder Benutzer hat kein TASK_WRITE

Merksatz:

401 bedeutet keine Authentifizierung; 403 bedeutet authentifiziert, aber blockiert oder CSRF abgelehnt.


43. Häufiger Fehler: CSRF in POST-Tests vergessen

Fehler:


@Test
@WithMockUser(authorities = "TASK_WRITE")
void createTask() throws Exception {
mockMvc.perform(post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Task A"}
"""))
.andExpect(status().isCreated());
}

Tatsächliches Ergebnis:


403 Forbidden

Fix:


.with(csrf())

wenn CSRF aktiviert ist.

Merksatz:

403 bei POST mit korrekter Authority bedeutet oft fehlendes CSRF.


44. Häufiger Fehler: Rollen falsch verwenden

Fehler:


@WithMockUser(roles = "ROLE_ADMIN")

Erwartet:


ROLE_ADMIN

Aber roles fügt ein Präfix hinzu und es kann werden:


ROLE_ROLE_ADMIN

Fix:


@WithMockUser(roles = "ADMIN")

oder:


@WithMockUser(authorities = "ROLE_ADMIN")

Merksatz:

Bei @WithMockUser sollten roles kein ROLE_ enthalten.


45. Häufiger Fehler: Methodensicherheit nicht aktiviert

Service:


@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
}

Test erwartet AccessDeniedException, aber die Methode läuft.

Möglicher Grund:


@EnableMethodSecurity fehlt

Fix:


@Configuration
@EnableMethodSecurity
public class SecurityConfig {
}

Merksatz:

@PreAuthorize braucht aktivierte Methodensicherheit.


46. Häufiger Fehler: Nicht-Spring-Instanz testen

Schlecht:


TaskService service = new TaskService(repository);
service.deleteTask(1L);

Das umgeht den Spring-Proxy.

Methodensicherheit greift eventuell nicht.

Richtig:


@Autowired
private TaskService taskService;

Nutze die Spring Bean aus dem Context.

Merksatz:

Methodensicherheit funktioniert bei Spring-verwalteten Beans, nicht bei manuell erstellten Objekten.


47. Häufiger Fehler: Self-Invocation

Service:


public void outer() {
inner();
}

@PreAuthorize("hasRole('ADMIN')")
public void inner() {
}

Wenn outer() inner() in derselben Klasse aufruft, kann Methodensicherheit umgangen werden.

Der Test sollte nicht nur inner() direkt testen.

Teste auch echte öffentliche Einstiegspunkte.

Merksatz:

Teste den Methodenpfad, den der Produktionscode wirklich nutzt.


48. Häufiger Fehler: @WithMockUser nutzt keine Datenbank

@WithMockUser erstellt einen Mock-Benutzer.

Er lädt keinen Benutzer aus der Datenbank.

Dieser Test:


@WithMockUser(username = "real@example.com")

beweist nicht:


real@example.com existiert in der Datenbank
Passwort ist korrekt
UserDetailsService funktioniert

Dafür nutze:


httpBasic()
formLogin()
eigenen Login-Endpunkt
@WithUserDetails
vollständigen Integrationstest

Merksatz:

@WithMockUser testet den Autorisierungskontext, nicht echten Login.


49. @WithUserDetails

@WithUserDetails lädt einen echten Benutzer über UserDetailsService.

Beispiel:


@Test
@WithUserDetails("user@example.com")
void realUserDetailsCanAccessEndpoint() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isOk());
}

Voraussetzung:


Benutzer muss in Test-Datenbank oder Test-UserDetailsService existieren

Merksatz:

@WithUserDetails nutzt den echten UserDetailsService.


50. @WithMockUser vs. @WithUserDetails

AnnotationBenutzerquelleGut für
@WithMockUsersimulierter Test-BenutzerAutorisierungstests
@WithUserDetailsechter UserDetailsServiceTests mit echtem Benutzer-Laden
eigenes @WithSecurityContexteigene Factorybenutzerdefinierte Principal-Tests

Merksatz:

Wähle die Test-Benutzer-Strategie danach, was du belegen musst.


51. Checkliste für Sicherheitstests

Nutze diese Checkliste:


[ ] Öffentliche Endpunkte sind anonym erreichbar.
[ ] Geschützte Endpunkte liefern anonym 401.
[ ] Authentifizierte Benutzer können erlaubte Endpunkte aufrufen.
[ ] Authentifizierte Benutzer ohne Berechtigung bekommen 403.
[ ] Admin-Endpunkte erfordern Admin-Rolle.
[ ] Rollen-Präfix wird korrekt getestet.
[ ] Authority-Regeln werden korrekt getestet.
[ ] POST/PUT/PATCH/DELETE-Tests enthalten CSRF, wenn CSRF aktiviert ist.
[ ] Fehlendes CSRF wird dort getestet, wo es wichtig ist.
[ ] Erlaubter Methodensicherheits-Fall ist getestet.
[ ] Verweigerter Methodensicherheits-Fall ist getestet.
[ ] Benutzerdefinierter Permission-Service ist getestet.
[ ] Principal-Tests nutzen den korrekten Principal-Typ.
[ ] JWT-Tests enthalten erforderliche Authorities/Scopes.
[ ] Security-Filter sind in MockMvc-Tests aktiv.
[ ] Tests decken Erfolgs- und Fehlerpfade ab.

52. Echte Prüfungsfrage: Security-Test-Abhängigkeit

Frage:

Welche Abhängigkeit liefert Spring-Security-Test-Helfer?

Antwort:

spring-security-test.


53. Echte Prüfungsfrage: @WithMockUser

Frage:

Was macht @WithMockUser?

Antwort:

Es führt den Test mit einem simulierten authentifizierten Benutzer im Security Context aus.


54. Echte Prüfungsfrage: Standard-@WithMockUser

Frage:

Welche Rolle hat der Standard-@WithMockUser?

Antwort:

Der Standard-Mock-Benutzer hat die Rolle USER, also die Authority ROLE_USER.


55. Echte Prüfungsfrage: CSRF in Tests

Frage:

Wie fügst du ein CSRF-Token in MockMvc hinzu?

Antwort:

Mit .with(csrf()).


56. Echte Prüfungsfrage: Fehlendes CSRF

Frage:

Welchen Status kann eine POST-Anfrage zurückgeben, wenn CSRF aktiviert ist und das Token fehlt?

Antwort:

403 Forbidden.


57. Echte Prüfungsfrage: 401 vs. 403 in Tests

Frage:

Wie testest du 401?

Antwort:

Sende die Anfrage ohne Authentifizierung.

Frage:

Wie testest du 403?

Antwort:

Authentifiziere dich als Benutzer ohne erforderliche Berechtigung, oder sende ein ungültiges/fehlendes CSRF-Token für unsichere Methoden, wenn CSRF aktiviert ist.


58. Echte Prüfungsfrage: Methodensicherheitstest

Frage:

Wie testest du eine mit @PreAuthorize geschützte Methode?

Antwort:

Rufe die Spring-verwaltete Service Bean mit einem simulierten authentifizierten Benutzer auf und prüfe entweder Erfolg oder AccessDeniedException.


59. Echte Prüfungsfrage: @WithUserDetails

Frage:

Was ist der Unterschied zwischen @WithMockUser und @WithUserDetails?

Antwort:

@WithMockUser erstellt einen simulierten Benutzer. @WithUserDetails lädt einen echten Benutzer über UserDetailsService.


60. Interviewantwort

Frage:

Wie testest du Endpunkt-Autorisierung mit MockMvc?

Gute Antwort:

Ich nutze MockMvc mit Spring-Security-Test-Support. Für anonymen Zugriff sende ich die Anfrage ohne Authentifizierung und erwarte 401 für geschützte Endpunkte. Für erlaubte Benutzer nutze ich @WithMockUser oder Request PostProcessors wie user() oder jwt() mit den erforderlichen Rollen oder Authorities und erwarte 200. Für verbotene Benutzer authentifiziere ich mit unzureichenden Authorities und erwarte 403. Bei unsicheren HTTP-Methoden füge ich .with(csrf()) hinzu, wenn CSRF aktiviert ist.


61. Interviewantwort

Frage:

Was ist der Unterschied zwischen @WithMockUser, httpBasic() und jwt() in Tests?

Gute Antwort:

@WithMockUser erstellt direkt einen simulierten authentifizierten Benutzer im Security Context — gut für Autorisierungstests. httpBasic() sendet Benutzername und Passwort durch den echten Basic-Authentifizierungsfluss und kann UserDetailsService und PasswordEncoder testen. jwt() erstellt eine simulierte JWT-Authentifizierung — nützlich für OAuth2 Resource Server oder Bearer-Token-Autorisierung ohne echtes Token.


62. Interviewantwort

Frage:

Warum schlagen POST-Tests oft mit 403 fehl?

Gute Antwort:

Wenn CSRF-Schutz aktiviert ist, brauchen unsichere HTTP-Methoden wie POST, PUT, PATCH und DELETE ein gültiges CSRF-Token. Auch wenn der Benutzer authentifiziert ist und die richtige Rolle hat, kann die Anfrage mit 403 fehlschlagen, wenn das CSRF-Token fehlt. In MockMvc-Tests füge ich .with(csrf()) hinzu.


63. Interviewantwort

Frage:

Wie testest du Methodensicherheit?

Gute Antwort:

Ich aktiviere Methodensicherheit mit @EnableMethodSecurity, lade die Spring-verwaltete Service Bean und nutze Annotationen wie @WithMockUser oder ein eigenes @WithSecurityContext, um den Security Context zu setzen. Dann teste ich erlaubte und verweigerte Fälle. Erlaubte Benutzer bekommen das Ergebnis; verweigerte Benutzer bekommen AccessDeniedException.


64. Interviewantwort

Frage:

Was sind häufige Fehler bei Sicherheitstests?

Gute Antwort:

Häufige Fehler: spring-security-test vergessen, MockMvc ohne Spring-Security-Filter bauen, @WithMockUser mit roles = "ROLE_ADMIN" nutzen, CSRF bei POST-Tests vergessen, 401 erwarten obwohl der Benutzer authentifiziert aber nicht berechtigt ist, manuell erstellte Services statt Spring Beans testen, @EnableMethodSecurity vergessen, und @WithMockUser nutzen wenn der Code einen benutzerdefinierten Principal braucht.


65. Kleine Code-Übung

Sicherheitsregel:


.requestMatchers(HttpMethod.GET, "/api/tasks/**").hasAuthority("TASK_READ")
.requestMatchers(HttpMethod.POST, "/api/tasks/**").hasAuthority("TASK_WRITE")
.requestMatchers(HttpMethod.DELETE, "/api/tasks/**").hasRole("ADMIN")

Schreibe Tests:


@Test
void anonymousCannotReadTasks() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isUnauthorized());
}

@Test
@WithMockUser(authorities = "TASK_READ")
void taskReaderCanReadTasks() throws Exception {
mockMvc.perform(get("/api/tasks"))
.andExpect(status().isOk());
}

@Test
@WithMockUser(authorities = "TASK_READ")
void taskReaderCannotCreateTask() throws Exception {
mockMvc.perform(post("/api/tasks")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Task A"}
"""))
.andExpect(status().isForbidden());
}

@Test
@WithMockUser(authorities = "TASK_WRITE")
void taskWriterCanCreateTask() throws Exception {
mockMvc.perform(post("/api/tasks")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Task A"}
"""))
.andExpect(status().isCreated());
}

@Test
@WithMockUser(roles = "ADMIN")
void adminCanDeleteTask() throws Exception {
mockMvc.perform(delete("/api/tasks/1")
.with(csrf()))
.andExpect(status().isNoContent());
}

Fragen:

  1. Welcher Test prüft 401?
  2. Welche Tests prüfen 403?
  3. Welche Tests brauchen CSRF?
  4. Warum nutzt Admin roles = "ADMIN"?
  5. Warum nutzt der Task-Reader authorities = "TASK_READ"?

Antworten:

  1. anonymousCannotReadTasks.
  2. taskReaderCannotCreateTask.
  3. POST- und DELETE-Tests, wenn CSRF aktiviert ist.
  4. Weil roles = "ADMIN" ROLE_ADMIN erzeugt.
  5. Weil die Regel die exakte Authority TASK_READ prüft.

66. Kleine Bug-Übung 1

Problem:


@Test
@WithMockUser(authorities = "TASK_WRITE")
void createTask() throws Exception {
mockMvc.perform(post("/api/tasks")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"title":"Task A"}
"""))
.andExpect(status().isCreated());
}

Tatsächliches Ergebnis:


403

Frage:

Was ist wahrscheinlich falsch?

Antwort:

CSRF-Token fehlt. Füge .with(csrf()) hinzu, wenn CSRF aktiviert ist.


67. Kleine Bug-Übung 2

Problem:


@Test
@WithMockUser(roles = "ROLE_ADMIN")
void adminCanDelete() throws Exception {
mockMvc.perform(delete("/api/tasks/1").with(csrf()))
.andExpect(status().isNoContent());
}

Frage:

Was ist falsch?

Antwort:

roles sollte kein ROLE_ enthalten. Nutze:


@WithMockUser(roles = "ADMIN")

oder:


@WithMockUser(authorities = "ROLE_ADMIN")

68. Kleine Bug-Übung 3

Problem:


TaskService taskService = new TaskService(taskRepository);
taskService.deleteTask(1L);

Frage:

Warum ist das schlecht für Methodensicherheitstests?

Antwort:

Der Service ist manuell erstellt, keine Spring-verwaltete Bean. Der Methodensicherheits-Proxy wird umgangen. Autowire die Service Bean aus dem Spring Context.


69. Kleine Bug-Übung 4

Problem:


@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) {
}

Test zeigt: USER kann die Methode trotzdem aufrufen.

Frage:

Was könnte fehlen?

Antwort:

@EnableMethodSecurity fehlt eventuell, oder die Methode wird nicht über einen Spring-verwalteten Proxy aufgerufen.


Übungsfragen

Frage 1

Welche Abhängigkeit liefert Spring-Security-Test-Support?

Antwort:

spring-security-test.


Frage 2

Was macht @WithMockUser?

Antwort:

Es führt den Test mit einem simulierten authentifizierten Benutzer im Security Context aus.


Frage 3

Welchen Benutzer erstellt der Standard-@WithMockUser?

Antwort:

Einen Benutzer mit Benutzername user, Passwort password und Rolle USER, also Authority ROLE_USER.


Frage 4

Was ist der Unterschied zwischen roles und authorities in @WithMockUser?

Antwort:

roles fügt automatisch das Präfix ROLE_ hinzu. authorities nutzt exakte Authority-Strings.


Frage 5

Wie testest du eine 401-Antwort?

Antwort:

Sende die Anfrage ohne Authentifizierung und erwarte status().isUnauthorized().


Frage 6

Wie testest du eine 403-Antwort?

Antwort:

Authentifiziere dich als Benutzer ohne ausreichende Berechtigung und erwarte status().isForbidden().


Frage 7

Wie fügst du ein CSRF-Token in MockMvc hinzu?

Antwort:

Mit .with(csrf()).


Frage 8

Wann brauchen POST-Tests CSRF?

Antwort:

Wenn CSRF-Schutz aktiviert ist und die Methode unsicher ist — z. B. POST, PUT, PATCH oder DELETE.


Frage 9

Was testet httpBasic()?

Antwort:

Es sendet Benutzername/Passwort über HTTP Basic und kann den Authentifizierungsfluss testen.


Frage 10

Was ist der Unterschied zwischen @WithMockUser und httpBasic()?

Antwort:

@WithMockUser erstellt direkt einen simulierten authentifizierten Benutzer. httpBasic() sendet Credentials durch den echten Basic-Authentifizierungsfluss.


Frage 11

Was macht @WithUserDetails?

Antwort:

Es lädt einen echten Benutzer über UserDetailsService.


Frage 12

Wie testest du einen JWT Resource Server Endpunkt?

Antwort:

Mit MockMvc und .with(jwt()), inklusive erforderlicher Authorities oder Claims.


Frage 13

Wie testest du Methodensicherheit?

Antwort:

Rufe die Spring-verwaltete Service Bean mit einem simulierten authentifizierten Benutzer auf und prüfe Erfolg oder AccessDeniedException.


Frage 14

Welche Exception erwartest du, wenn Methodensicherheit den Zugriff verweigert?

Antwort:

AccessDeniedException.


Frage 15

Warum funktioniert @WithMockUser eventuell nicht mit benutzerdefinierten Principal-Ausdrücken?

Antwort:

Weil @WithMockUser einen Standard-Mock-Benutzer erstellt, nicht deinen benutzerdefinierten Principal mit Feldern wie id oder tenantId.


Frage 16

Wie testest du mit einem benutzerdefinierten Principal in MockMvc?

Antwort:

Mit .with(user(customPrincipal)).


Frage 17

Wofür ist @WithSecurityContext nützlich?

Antwort:

Zum Erstellen eigener Security-Context-Annotationen für Tests — besonders wenn ein benutzerdefinierter Principal nötig ist.


Frage 18

Warum können MockMvc-Tests bestehen, obwohl echte Security die Anfrage blockieren würde?

Antwort:

Weil MockMvc ohne Spring-Security-Filter gebaut werden kann — z. B. mit Standalone-Setup ohne springSecurity().


Frage 19

Warum ist @WithMockUser(roles = "ROLE_ADMIN") falsch?

Antwort:

Weil roles automatisch ROLE_ hinzufügt — es kann ROLE_ROLE_ADMIN entstehen.


Frage 20

Warum solltest du erlaubte und verweigerte Fälle testen?

Antwort:

Weil Security beweisen muss, dass richtige Benutzer zugelassen und falsche Benutzer blockiert werden.

Merksätze zum Mitnehmen

  • Sicherheitstests belegen Zugriffsregeln.
  • spring-security-test hinzufügen.
  • MockMvc kann gesicherte Endpunkte testen.
  • Wenn MockMvc manuell gebaut wird, springSecurity() anwenden.
  • @WithMockUser erstellt einen simulierten authentifizierten Benutzer.
  • Standard-@WithMockUser hat Rolle USER.
  • roles = "ADMIN" erzeugt ROLE_ADMIN.
  • authorities = "TASK_READ" erzeugt exakte Authority TASK_READ.
  • 401 ohne Authentifizierung testen.
  • 403 mit authentifiziertem Benutzer ohne Berechtigung testen.
  • POST/PUT/PATCH/DELETE brauchen .with(csrf()), wenn CSRF aktiviert ist.
  • permitAll deaktiviert CSRF nicht automatisch.
  • httpBasic() testet den Basic-Authentifizierungsfluss.
  • @WithMockUser testet keine echte Passwort-Authentifizierung.
  • @WithUserDetails nutzt echten UserDetailsService.
  • jwt() testet JWT Resource Server Autorisierung.
  • Methodensicherheitstests rufen Spring-verwaltete Service Beans auf.
  • Verweigerte Methodensicherheit wirft normalerweise AccessDeniedException.
  • @EnableMethodSecurity ist für @PreAuthorize erforderlich.
  • Benutzerdefinierte Principals brauchen eventuell .with(user(customPrincipal)) oder eigenes @WithSecurityContext.
  • Immer erlaubte und verweigerte Fälle testen.