Woche 6, Tag 5 — Security Testing
Ziel
Heute lernst du, wie du Spring Security testest.
Die Kernfragen:
- Warum sind Sicherheitstests wichtig?
- Welche Abhängigkeit brauchst du?
- Wie testest du gesicherte Controller mit MockMvc?
- Was ist
@WithMockUser? - Wie testest du Rollen und Authorities?
- Wie testest du 401 und 403?
- Wie testest du CSRF?
- Wie testest du HTTP Basic?
- Wie testest du Methodensicherheit?
- Wie testest du
@PreAuthorize? - Wie testest du benutzerdefinierte Principal-Logik?
- 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.STATELESSbedeutet: 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-testliefert 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:
@WithMockUsererstellt 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 AuthorityROLE_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
rolesROLE_hinzu;authoritiessind 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
@WithMockUserfü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:
permitAlldeaktiviert 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:
permitAllumgeht 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:
@WithAnonymousUsermacht 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 = "...")steuertauthentication.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:
@WithMockUserreicht 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
@WithSecurityContextfü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:
@WebMvcTestist 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
@SpringBootTestfür vollständige Security-Integrationstests.
38. Testtyp wählen
| Testtyp | Gut für |
|---|---|
@WebMvcTest | Controller + URL-Security-Slice |
@SpringBootTest | vollständige Security-Integration |
Service-Test mit @WithMockUser | Methodensicherheit |
MockMvc mit jwt() | Resource-Server-Autorisierung |
MockMvc mit httpBasic() | Basic-Authentifizierungsfluss |
eigenes @WithSecurityContext | benutzerdefinierte 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
@AuthenticationPrincipalteste 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
@WithMockUsersolltenroleskeinROLE_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:
@PreAuthorizebraucht 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:
@WithMockUsertestet 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:
@WithUserDetailsnutzt den echtenUserDetailsService.
50. @WithMockUser vs. @WithUserDetails
| Annotation | Benutzerquelle | Gut für |
|---|---|---|
@WithMockUser | simulierter Test-Benutzer | Autorisierungstests |
@WithUserDetails | echter UserDetailsService | Tests mit echtem Benutzer-Laden |
eigenes @WithSecurityContext | eigene Factory | benutzerdefinierte 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()undjwt()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:
- Welcher Test prüft 401?
- Welche Tests prüfen 403?
- Welche Tests brauchen CSRF?
- Warum nutzt Admin
roles = "ADMIN"? - Warum nutzt der Task-Reader
authorities = "TASK_READ"?
Antworten:
anonymousCannotReadTasks.taskReaderCannotCreateTask.- POST- und DELETE-Tests, wenn CSRF aktiviert ist.
- Weil
roles = "ADMIN"ROLE_ADMINerzeugt. - Weil die Regel die exakte Authority
TASK_READprü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-testhinzufügen.- MockMvc kann gesicherte Endpunkte testen.
- Wenn MockMvc manuell gebaut wird,
springSecurity()anwenden. @WithMockUsererstellt einen simulierten authentifizierten Benutzer.- Standard-
@WithMockUserhat RolleUSER. roles = "ADMIN"erzeugtROLE_ADMIN.authorities = "TASK_READ"erzeugt exakte AuthorityTASK_READ.- 401 ohne Authentifizierung testen.
- 403 mit authentifiziertem Benutzer ohne Berechtigung testen.
- POST/PUT/PATCH/DELETE brauchen
.with(csrf()), wenn CSRF aktiviert ist. permitAlldeaktiviert CSRF nicht automatisch.httpBasic()testet den Basic-Authentifizierungsfluss.@WithMockUsertestet keine echte Passwort-Authentifizierung.@WithUserDetailsnutzt echtenUserDetailsService.jwt()testet JWT Resource Server Autorisierung.- Methodensicherheitstests rufen Spring-verwaltete Service Beans auf.
- Verweigerte Methodensicherheit wirft normalerweise
AccessDeniedException. @EnableMethodSecurityist für@PreAuthorizeerforderlich.- Benutzerdefinierte Principals brauchen eventuell
.with(user(customPrincipal))oder eigenes@WithSecurityContext. - Immer erlaubte und verweigerte Fälle testen.