Übungsaufgaben zum Change Request Authentifizierung: Die Mitfahrgesuche absichern
https://bildung.social/@oerinformatik/
https://oer-informatik.de/spring-boot-change-request-authentifizierung-uebungsaufgaben
Change Request, Auswirkungsanalyse, Regressionstest, Spring Security, HTTP Basic, Authentifizierung, Autorisierung, WithMockUser, Übungsaufgaben
tl/dr; (ca. 34 min Lese- und Bearbeitungszeit): Übungen zum Änderungsantrag „Anmeldung statt Nutzer-ID“ im Mitfahr-Backend: Du ordnest Anfragen den Statuscodes 401 und 403 zu, analysierst die Auswirkungen auf die Mitfahrgesuche, ergänzt die Regeln der
SecurityFilterChain, lässt nur eigene Gesuche zurückziehen, testest den Controller mit@WithMockUserund bewertest HTTP Basic gegenüber OAuth2 [AI-Assisted]. (Zuletzt geändert am 11.11.2026)
Dieser Artikel ist Teil einer Artikelreihe über ein REST-Backend mit Spring Boot: Überblick und Fahrplan / 01 Anforderungen und API-Vertrag / 02 Projekt anlegen / 03 Codegenerierung / 04 Fachlogik und Komponententest / 05 Build-Qualität und CI / 06 Persistenz mit JPA / 07 Repository-Integrationstest / 08 Service und Dependency Injection / 09 REST-API und Fehlerbehandlung / 10 Controller-Integrationstest / 11 System- und Abnahmetest / 12 Change Request Authentifizierung / Übungsaufgaben zu 12
Diese Übungsaufgaben gehören zum Artikel Change Request: Anmeldung mit Spring Security nachrüsten und alle Testebenen als Regressionstest nutzen und setzen den Übungsstrang zu den Mitfahrgesuchen fort. Ausgangspunkt ist das Projekt im Ordner src/12-change-request-authentifizierung des Repositorys mit Anmeldung per HTTP Basic (Passwort aller Demo-Konten: mitfahren), ergänzt um die Klassen aus den bisherigen Übungen: die Entity Gesuch mit zieheZurueck(), GesuchRepository, NeuesGesuch, GesuchService, GesuchController und den Vertrag mit POST /v1/gesuche und PATCH /v1/gesuche/{gesuchId}.
Der Änderungsantrag CR-01 aus dem Artikel gilt auch für die Gesuche:
Wer ein Gesuch einstellt, ist die angemeldete Person; die
mitfahrerIdsteht nicht mehr im Body, sondern nur noch in der Antwort. Ein Gesuch zurückziehen darf nur, wer es eingestellt hat. Das Organisationsteam (Rolle ORGA, PseudonymAdmin-AW) stellt keine Gesuche ein. Der Flurmonitor bleibt ohne Anmeldung lesbar.
Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1 und Aufgabe 1.2. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1, Aufgabe 2.2 und Aufgabe 2.3. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1) keine Probleme bereiten. Notiere Deine Antworten zuerst vollständig, bevor Du die Beispiellösung aufklappst. Code und Tests kannst Du anschließend im Projekt aus dem Repository mit dem Build ausführen; andere fachlich richtige Lösungen sind ebenso richtig.
Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?
Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.
Erstelle neue Übungsaufgaben zum Thema „Change Request
Authentifizierung: Die Mitfahrgesuche absichern“.
Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.
Fachliche Grundlage:
- https://oer-informatik.de/spring-boot-change-request-authentifizierung
- https://oer-informatik.de/authentifizierung
- https://oer-informatik.de/passwort-mfa
- https://oer-informatik.de/v-modell
- Stilbeispiel: https://oer-informatik.de/spring-boot-change-request-authentifizierung-uebungsaufgaben
Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Change Request
- Auswirkungsanalyse
- Regressionstest
- Spring Security
- HTTP Basic
- Authentifizierung
- Autorisierung
- WithMockUser
Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan
Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
danach frage.
Aufgabe 1.1: 401, 403 oder Erfolg? (Basiskompetenz)
Gib für jede Anfrage an das Backend nach CR-01 den Statuscode an und begründe kurz. Oksana (Pseudonym Kaffeetasse, Nutzerin 3) hat Gesuch 1 eingestellt; es ist offen. Lukas hat das Pseudonym Bassline. (6 Punkte)
| Nr. | Anfrage | Status | Begründung |
|---|---|---|---|
| a) | GET /v1/monitor/eintraege ohne Authorization-Header |
||
| b) | POST /v1/gesuche ohne Authorization-Header |
||
| c) | POST /v1/gesuche als Kaffeetasse mit dem Passwort mitfahrn |
||
| d) | POST /v1/gesuche als Admin-AW mit richtigem Passwort |
||
| e) | PATCH /v1/gesuche/1 mit {"status": "zurueckgezogen"} als Bassline |
||
| f) | dieselbe Anfrage als Kaffeetasse |
| Nr. | Status | Begründung |
|---|---|---|
| a) | 200 | Der Flurmonitor ist öffentlich (permitAll, im Vertrag security: []). |
| b) | 401 | Keine Anmeldung; die Antwort enthält WWW-Authenticate: Basic. |
| c) | 401 | Falsches Passwort: Die Authentifizierung scheitert, die Person gilt als nicht angemeldet. |
| d) | 403 | Angemeldet, aber die Rolle ORGA erfüllt die Regel hasRole("NUTZER") nicht. |
| e) | 403 | Angemeldet als Lukas, das Gesuch gehört Oksana; der Service wirft KeineBerechtigungException. |
| f) | 200 | Oksana zieht ihr eigenes offenes Gesuch zurück (Status danach zurueckgezogen). |
Je Zeile 1 Punkt (Status und passende Begründung). Merkhilfe: 401 heißt „Wer bist Du?“, 403 heißt „Das darfst Du nicht“.
Aufgabe 1.2: Auswirkungsanalyse für die Gesuche (Basiskompetenz)
Die Auswirkungsanalyse im Artikel hat die Gesuche noch nicht berücksichtigt.
Nenne vier Artefakte des Gesuch-Strangs (Vertrag, Klassen, Tests), die CR-01 ändert, und je die Änderung. (4 Punkte)
Ist die Änderung von
POST /v1/gesuchefür ein Frontend kompatibel? Begründe und nenne die Folge für die Versionsnummer des Vertrags. (2 Punkte)
- Zum Beispiel (je Artefakt mit passender Änderung 1 Punkt):
| Artefakt | Änderung |
|---|---|
openapi.yaml, Schema GesuchNeu |
mitfahrerId entfällt; sie steht nur noch im Schema Gesuch der Antwort |
openapi.yaml, POST /gesuche und PATCH /gesuche/{gesuchId} |
Antworten 401 und 403 ergänzen |
GesuchController |
Nutzer-ID über AktuelleAnmeldung statt aus dem Body |
GesuchService.zieheZurueck |
bekommt die Anmeldung und prüft, ob das Gesuch der Person gehört |
SicherheitsKonfiguration |
Regel POST /v1/gesuche nur für die Rolle NUTZER |
GesuchControllerTest |
@Import(SicherheitsKonfiguration.class), @WithMockUser, Mock für AktuelleAnmeldung |
GesuchAbnahmeTest (AK-09.x) |
Anfragen als Oksana mit Basic-Header, kein mitfahrerId im JSON |
- Nicht kompatibel (Breaking Change): Ein Frontend nach dem alten Vertrag schickt keine Anmeldung und bekommt 401; die bisher verpflichtende
mitfahrerIdwird nicht mehr ausgewertet. Nach Semantic Versioning steigt die Hauptversion (hier 1.0.0 → 2.0.0). (je 1 Punkt)
Aufgabe 2.1: Regeln in der SecurityFilterChain ergänzen (Kernkompetenz)
Ausgangspunkt ist die SicherheitsKonfiguration aus dem Artikel:
http.authorizeHttpRequests(regeln -> regeln
.requestMatchers(HttpMethod.GET, "/v1/haltepunkte/**", "/v1/monitor/**").permitAll()
.requestMatchers(HttpMethod.POST, "/v1/angebote", "/v1/angebote/*/buchungen").hasRole("NUTZER")
.requestMatchers("/error", "/h2-console/**").permitAll()
.anyRequest().authenticated())Ergänze die Zeile, mit der nur die Rolle NUTZER Gesuche einstellen darf, und gib an, wo sie stehen muss. (2 Punkte)
Ein Teammitglied hängt die neue Zeile hinter
.anyRequest().authenticated()an. Was passiert beim Start der Anwendung bzw. der Controller-Tests, und warum ist das sinnvoll? (2 Punkte)Was würde passieren, wenn die Zeile aus a) ganz fehlt? Welcher Test findet das? (2 Punkte)
- Vor
anyRequest(), zum Beispiel direkt unter der Regel für Angebote (1 Punkt für die Zeile, 1 Punkt für die Position):
.requestMatchers(HttpMethod.POST, "/v1/gesuche").hasRole("NUTZER")Ebenso richtig: /v1/gesuche in die vorhandene Regel für POST aufnehmen.
- Der Kontext startet nicht; die Controller-Tests scheitern mit (1 Punkt):
Caused by: java.lang.IllegalStateException: Can't configure requestMatchers after anyRequest
Die Regeln gelten in ihrer Reihenfolge, die erste passende entscheidet. Eine Regel hinter anyRequest() könnte nie greifen; Spring Security meldet den Fehler deshalb sofort, statt eine wirkungslose Regel still hinzunehmen (1 Punkt).
- Dann greift
anyRequest().authenticated(): Jede angemeldete Person dürfte Gesuche einstellen, auchAdmin-AW(201 statt 403) (1 Punkt). Das findet ein Test, der die Rolle ORGA prüft, zum Beispiel ein Controller-Test mit@WithMockUser(roles = "ORGA")und erwartetem 403 oder ein Abnahmetest zu CR-01 (1 Punkt). Ein Test nur mit Oksana bliebe grün.
Aufgabe 2.2: Nur eigene Gesuche zurückziehen (Kernkompetenz)
Bisher sieht zieheZurueck im GesuchService so aus:
@Transactional
public Gesuch zieheZurueck(Long gesuchId) {
Gesuch gesuch = gesuchRepository.findById(gesuchId)
.orElseThrow(() -> new NichtGefundenException("Gesuch", gesuchId));
gesuch.zieheZurueck();
return gesuch;
}Ändere die Methode so, dass nur die Person, die das Gesuch eingestellt hat, es zurückziehen darf. Andernfalls soll eine
KeineBerechtigungExceptionmit der Meldung „Nur wer das Gesuch 1 eingestellt hat, darf es zurückziehen.“ (für Gesuch 1) entstehen. Die KlasseAnmeldungaus dem Artikel steht zur Verfügung. (3 Punkte)Schreibe zwei Komponententests mit Mockito: Oksana zieht ihr Gesuch zurück, und Lukas versucht es vergeblich. Prüfe im zweiten Test auch, dass das Gesuch offen bleibt. Das Gesuch legst Du im Test mit
new Gesuch(oksana, guetersloh, Fahrtrichtung.ZUR_SCHULE, LocalDate.of(2026, 11, 23), LocalTime.of(6, 45), LocalTime.of(7, 15))an. (4 Punkte)
- (3 Punkte: Parameter
Anmeldung1, Vergleich der Nutzer-IDs vor dem Statuswechsel 1, Ausnahme mit Meldung 1)
@Transactional
public Gesuch zieheZurueck(Long gesuchId, Anmeldung anmeldung) {
Gesuch gesuch = gesuchRepository.findById(gesuchId)
.orElseThrow(() -> new NichtGefundenException("Gesuch", gesuchId));
if (!gesuch.getNutzer().getId().equals(anmeldung.nutzerId())) {
throw new KeineBerechtigungException("Nur wer das Gesuch " + gesuchId + " eingestellt hat, darf es "
+ "zurückziehen.");
}
gesuch.zieheZurueck();
return gesuch;
}Die Prüfung steht vor gesuch.zieheZurueck(). Stünde sie dahinter, würde die Transaktion zwar wegen der Ausnahme zurückgerollt, aber das Objekt im Speicher wäre schon geändert. Der GesuchController übergibt aktuelleAnmeldung.anmeldung().
- (4 Punkte: Mock und Testobjekt mit ID 1, Erfolgsfall mit Statusprüfung 1, Ausnahme mit Typ und Meldung 1, Status bleibt
OFFEN1)
@ExtendWith(MockitoExtension.class)
class GesuchServiceTest {
private static final Anmeldung OKSANA = new Anmeldung(3L, Rolle.NUTZER);
private static final Anmeldung LUKAS = new Anmeldung(4L, Rolle.NUTZER);
@Mock
private GesuchRepository gesuchRepository;
@InjectMocks
private GesuchService gesuchService;
private Gesuch gesuch;
@BeforeEach
void oksanasGesuch() {
var oksana = new Nutzer("Kaffeetasse", "Oksana", "Melnyk", LocalDate.of(2003, 4, 28), null, false);
ReflectionTestUtils.setField(oksana, "id", 3L);
var guetersloh = new Haltepunkt("Hauptbahnhof, ZOB Steig 3", "Gütersloh", new BigDecimal("18.6"));
gesuch = new Gesuch(oksana, guetersloh, Fahrtrichtung.ZUR_SCHULE, LocalDate.of(2026, 11, 23),
LocalTime.of(6, 45), LocalTime.of(7, 15));
when(gesuchRepository.findById(1L)).thenReturn(Optional.of(gesuch));
}
@Test
void eigenesGesuchZurueckziehen() {
gesuchService.zieheZurueck(1L, OKSANA);
assertThat(gesuch.getStatus()).isEqualTo(Gesuchstatus.ZURUECKGEZOGEN);
}
@Test
void fremdesGesuchBleibtOffen() {
assertThatThrownBy(() -> gesuchService.zieheZurueck(1L, LUKAS))
.isInstanceOf(KeineBerechtigungException.class)
.hasMessage("Nur wer das Gesuch 1 eingestellt hat, darf es zurückziehen.");
assertThat(gesuch.getStatus()).isEqualTo(Gesuchstatus.OFFEN);
}
}Wird der GesuchService in Deinem Projekt mit weiteren Abhängigkeiten (z. B. Clock) per Konstruktor gebaut, legst Du ihn wie im BuchungServiceTest in @BeforeEach mit new an. Im Projekt ausgeführt sind beide Tests grün.
Aufgabe 2.3: Den GesuchController mit Anmeldung testen (Kernkompetenz)
Schreibe eine Klasse GesuchControllerTest mit @WebMvcTest und drei Tests für POST /v1/gesuche mit dem Body {"haltepunktId": 1, "richtung": "zur-schule", "datum": "2026-11-24", "zeitVon": "06:45", "zeitBis": "07:15"}: (6 Punkte)
- Oksana ist angemeldet: Antwort 201,
mitfahrerId3 in der Antwort, und der Service bekommt einNeuesGesuchmit Nutzer-ID 3. - Ohne Anmeldung: 401, der Service wird nicht aufgerufen.
- Angemeldet mit der Rolle ORGA: 403, der Service wird nicht aufgerufen.
Ein Hilfsobjekt gesuch4() (Gesuch 4 von Oksana am 24.11.2026) darfst Du voraussetzen; Importe musst Du nicht notieren.
(6 Punkte: @Import(SicherheitsKonfiguration.class) 1, Mocks für Service und AktuelleAnmeldung 1, @WithMockUser mit Rolle bzw. @WithAnonymousUser 1, Test 1 mit Status, Body und verify 1, Test 2 1, Test 3 1)
@WebMvcTest(GesuchController.class)
@Import(SicherheitsKonfiguration.class)
class GesuchControllerTest {
private static final String NEUES_GESUCH = """
{"haltepunktId": 1, "richtung": "zur-schule", "datum": "2026-11-24",
"zeitVon": "06:45", "zeitBis": "07:15"}
""";
@Autowired
private MockMvcTester mvc;
@MockitoBean
private GesuchService gesuchService;
@MockitoBean
private AktuelleAnmeldung aktuelleAnmeldung;
@Test
@WithMockUser(username = "Kaffeetasse", roles = "NUTZER")
void gesuchGehoertDerAngemeldetenPerson() {
when(aktuelleAnmeldung.anmeldung()).thenReturn(new Anmeldung(3L, Rolle.NUTZER));
when(gesuchService.stelleEin(any())).thenReturn(gesuch4());
assertThat(mvc.post().uri("/v1/gesuche").contentType(MediaType.APPLICATION_JSON).content(NEUES_GESUCH))
.hasStatus(HttpStatus.CREATED)
.bodyJson().extractingPath("$.mitfahrerId").isEqualTo(3);
verify(gesuchService).stelleEin(new NeuesGesuch(3L, 1L, Fahrtrichtung.ZUR_SCHULE,
LocalDate.of(2026, 11, 24), LocalTime.of(6, 45), LocalTime.of(7, 15)));
}
@Test
@WithAnonymousUser
void ohneAnmeldungErgibt401() {
assertThat(mvc.post().uri("/v1/gesuche").contentType(MediaType.APPLICATION_JSON).content(NEUES_GESUCH))
.hasStatus(HttpStatus.UNAUTHORIZED);
verifyNoInteractions(gesuchService);
}
@Test
@WithMockUser(username = "Admin-AW", roles = "ORGA")
void orgaStelltKeineGesucheEin() {
assertThat(mvc.post().uri("/v1/gesuche").contentType(MediaType.APPLICATION_JSON).content(NEUES_GESUCH))
.hasStatus(HttpStatus.FORBIDDEN);
verifyNoInteractions(gesuchService);
}
}Der zweite Test kommt ohne Mock-Verhalten aus: Die Filterkette weist die Anfrage ab, bevor der Controller läuft. Der erste Test zeigt, dass der Controller die Nutzer-ID aus der Anmeldung nimmt; ein Test, der nur den Status prüft, würde einen Controller, der weiter eine feste ID einsetzt, nicht bemerken. Im Projekt ausgeführt sind alle drei Tests grün.
Aufgabe 3.1: HTTP Basic bewerten (Profilkompetenz)
In einem Mitschnitt des Netzwerkverkehrs im Schul-WLAN findet sich bei einer Anfrage an das Mitfahr-Backend über http:// diese Zeile:
Authorization: Basic S2FmZmVldGFzc2U6bWl0ZmFocmVu
Was lässt sich aus der Zeile ablesen, und was folgt daraus für den Betrieb? (2 Punkte)
Die Schulleitung möchte das Backend an der Schule einsetzen. Nenne zwei Schwächen von HTTP Basic für diesen Einsatz, die auch HTTPS nicht behebt. Beschreibe dann, was sich am Backend ändert, wenn die Anmeldung auf Keycloak mit OAuth2/OpenID Connect umgestellt wird, und was gleich bleibt. (4 Punkte)
Base64 ist keine Verschlüsselung: Dekodiert ergibt der Wert
Kaffeetasse:mitfahren, also Pseudonym und Passwort von Oksana (1 Punkt). Wer mitliest, kann sich als Oksana anmelden. HTTP Basic darf deshalb nur über HTTPS laufen; das Passwort muss geändert werden, und Demo-Passwörter gehören nicht in den Betrieb (1 Punkt).Schwächen (2 Punkte, je 1), zum Beispiel:
- Das Frontend muss das Passwort speichern und bei jeder Anfrage mitschicken; es gibt kein Abmelden und keine Ablaufzeit.
- Jede Anwendung hat eigene Konten und Passwörter; keine zentrale Anmeldung mit dem Schulkonto, keine MFA.
- Jede Anfrage prüft das Passwort mit BCrypt, das kostet bei vielen Anfragen spürbar Rechenzeit.
Mit Keycloak (2 Punkte): Die Anmeldung samt Passwortprüfung (und ggf. MFA) übernimmt Keycloak; das Frontend schickt ein zeitlich begrenztes Token (JWT) als Authorization: Bearer …. Das Backend wird zum OAuth2 Resource Server: Es prüft nur Signatur, Ablauf und Rollen des Tokens; NutzerAnmeldung, Passwort-Hashes in data.sql und httpBasic() entfallen, im Vertrag wird type: oauth2 bzw. openIdConnect beschrieben. Gleich bleiben die Regeln nach Pfad und Rolle in der SecurityFilterChain, die Prüfung „eigenes Angebot, eigenes Gesuch“ im Service, die Statuscodes 401/403 und die meisten Tests, weil sie nur Nutzer-ID und Rolle brauchen.
Links und weitere Informationen
zum Lehrartikel: Change Request: Anmeldung mit Spring Security nachrüsten
als Grundlage: Authentifizierung, Passwörter und MFA, V-Modell
als Nachschlagewerk: Spring Security – Autorisierung von HTTP-Anfragen, Spring Security – Testen mit @WithMockUser, RFC 7617 – The „Basic“ HTTP Authentication Scheme
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zum Change Request Authentifizierung: Die Mitfahrgesuche absichern” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/spring-boot-change-request-authentifizierung-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
