CodeGym /Kurse /Spring Security /formLogin () und POST /logout in Tests

formLogin () und POST /logout in Tests

Spring Security
Level 27, Lektion 1
Verfügbar

1. Rolle der Login/Logout-Tests

Wenn Sie gerade gelernt haben, Tests mit .with(user("maria").roles("USER")) zu schreiben, kommt der natürliche Gedanke: „Na gut, die Sicherheit ist getestet, man kann feiern.“ Aber es gibt einen Haken. Ein solcher Test prüft Autorisierungsregeln (wen wir wohin lassen), aber fast nicht den Authentifizierungsablauf (wie eine Person überhaupt eingeloggt wird). Und das sind zwei verschiedene Teile des Systems – wie „Werksausweis“ und „Passkontrolle am Eingang“.

Das gefährlichste Szenario für ein Projekt sieht so aus: Sie haben grüne Tests für /api/me und /api/admin/users, weil Sie überall user() verwenden – und das erstellt ehrlich einen authentifizierten SecurityContext direkt innerhalb der Testanfrage. Aber in der Realität ist Ihr formLogin kaputt: der falsche PasswordEncoder, der falsche UserDetailsService, die falsche Login-URL, Sie haben den Parameter username auf email geändert und vergessen, oder jemand hat die Konfiguration so „optimiert“, dass UsernamePasswordAuthenticationFilter nicht mehr ausgelöst wird. Am Ende kann ein echter Benutzer im Browser nicht einloggen … während die Tests fröhlich lächeln und tun, als wäre alles in Ordnung.

Daher ist die Logik einfach: user()-Tests sind schnelle und umfassende Überprüfungen der Zugriffsmatrix, und formLogin()- und Logout-Tests sind einige punktuelle, aber sehr wichtige „durchgängige“ Überprüfungen, die garantieren, dass Ihr stateful Login/Logout wirklich funktioniert.

2. formLogin() in spring-security-test

Wenn ein Anfänger zum ersten Mal versucht, das Login manuell zu testen, macht er normalerweise post("/login"), fügt die Parameter username und password hinzu und … bekommt 403 Forbidden. Dann beginnt das Klassische: „Spring Security ist kaputt“, „CSRF nervt“, „lass uns CSRF ausschalten“, „ich habe im Internet .csrf().disable() gesehen“. Und in diesem Moment wird der Lehrer traurig, und irgendwo auf der Welt weint ein sicherer Backend.

formLogin() ist ein fertiger Builder aus spring-security-test, der eine korrekte formularbasierte Login-Anfrage für Sie zusammenstellt. Er macht das, was im Browser normalerweise ein HTML-Formular macht: sendet einen POST an die Login-Processing-URL, fügt die Parameter für Login und Passwort hinzu, setzt den richtigen Content-Type und – am wichtigsten in unserem Kontext – fügt ein gültiges CSRF-Token hinzu, weil Login eine zustandsändernde Operation ist.

Es ist nützlich im Kopf zu behalten, dass formLogin() kein „magischer Knopf „logge mich ein“ ist, sondern nur eine bequeme Möglichkeit, nicht immer das Gleiche manuell schreiben zu müssen. Sinngemäß simuliert er eine solche Anfrage:

Was Typischer Wert
HTTP-Methode POST
URL /login (Standard)
Content-Type application/x-www-form-urlencoded
Login-Parameter username=... (Standard)
Passwort-Parameter password=... (Standard)
CSRF vorhanden (sonst
403
)

Wenn Sie die Einstellungen von formLogin (URL oder Parameternamen) geändert haben, muss sich der Test zusammen mit ihnen ändern – dazu später mehr. Zunächst ist unser Ziel, das erfolgreiche und erfolglose Einloggen zu testen, ohne den Test zu 30 Zeilen manueller Anfragenzusammenstellung zu machen.

3. Grundgerüst der Tests mit MockMvc

Bevor wir Tests für Login/Logout schreiben, ist es wichtig, dass wir ein MockMvc haben, das wirklich durch die SecurityFilterChain läuft. Dieses Grundgerüst haben Sie bereits seit dem letzten Tag, aber ich zeige es noch einmal kurz – einfach, damit der Code nicht in der Luft hängt, wie ein Bug in der Produktion am Freitagabend.

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest // Wir starten den echten Spring-Kontext, damit SecurityFilterChain echt ist
@AutoConfigureMockMvc // Wir bitten Spring, MockMvc über die Filter (einschließlich Spring Security) zu konfigurieren
class AuthFlowTest {

    @Autowired
    MockMvc mvc; // MockMvc wird Anfragen durch die gesamte Filterkette leiten, nicht „drumherum“
}

Das ist die Basisform: Wir starten den Anwendungskontext und erhalten MockMvc. Weiterhin ist es wichtig, eine praktische Sache im Kopf zu behalten: formLogin() führt eine echte Authentifizierung durch, d.h. Ihre Anwendung benötigt im Testprofil einen echten Benutzer im User Store (In-Memory oder DB). Anders als bei user() reicht hier „der Benutzer im Kopf des Tests“ nicht – Spring Security wird ihn ehrlich über den UserDetailsService suchen.

Wenn Sie in den Tests keinen vorbereiteten Benutzer maria mit Passwort secret haben, dann wird formLogin().user("maria").password("secret") kein „grüner Test“ sein, sondern eine „schöne Art, BadCredentials zu sagen“.

4. Erfolgreiches Login in Tests

Erfolgreiches Login im stateful Modell ist nicht „wir haben 200 OK bekommen“. Es ist der Übergang des Systems vom Zustand „anonymous“ zu „authenticated“, plus die Erstellung/Nutzung einer session, plus das Standard-Browser-orientierte Verhalten (normalerweise Redirect). Und wir möchten genau dieses Verhalten im Test festhalten, nicht nur die Tatsache, dass der Server etwas geantwortet hat.

Der kompakteste und nützlichste Test für erfolgreiches Login prüft normalerweise drei Dinge: Status (eher 3xx), Redirect-URL (falls erwartet) und die Tatsache der Authentifizierung (über den Matcher authenticated()). Hier ein Beispiel:

import org.junit.jupiter.api.Test; 

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

class AuthFlowTest {

    // mvc bereits via @Autowired injiziert

    @Test
    void loginWhenValidCredentialsThenRedirectAndAuthenticated() throws Exception {
        // formLogin() simuliert das browserseitige Absenden des Formulars (inkl. CSRF), nicht ein „Login aus dem Testkopf heraus“
        mvc.perform(formLogin().user("maria").password("secret"))
                .andExpect(status().is3xxRedirection()) // Für Form-Login erwarten wir normalerweise einen Redirect, nicht 200
                .andExpect(redirectedUrl("/")) // Typisches Standardverhalten: nach Login zur Startseite
                .andExpect(authenticated().withUsername("maria")); // Wir stellen die Authentifizierung im SecurityContext fest
    }
}

Warum ist authenticated() hier wichtig? Weil es eine Prüfung auf Spring-Security-Ebene ist, nicht auf der Ebene „na ja, Redirect scheint da zu sein“. In der Realität kann man auch bei fehlgeschlagener Authentifizierung einen Redirect bekommen (z.B. auf /login?error). Daher ist es gut, wenn der Test explizit festhält: „Der Benutzer wurde tatsächlich authentifiziert“.

Wenn Sie den Test etwas „physikalischer“ gestalten möchten (und das ist für Anfänger nützlich), können Sie zusätzlich prüfen, ob eine session entstanden ist und ob man mit ihr nun einen geschützten Endpunkt aufrufen kann. Wichtig ist, den Test nicht in eine Filmtrilogie zu verwandeln, aber ein kurzes Szenario „Login → Zugriff auf /api/me“ ergibt ein sehr klares Bild.

import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@Test
void meEndpointWhenLoginThenOk() throws Exception {
    // Schritt 1: Wir loggen uns „wirklich“ ein, damit Spring Security eine Session für uns erstellt
    MvcResult login = mvc.perform(formLogin().user("maria").password("secret"))
            .andReturn();

    // Wir holen die Session aus der Login-Anfrage – das ist unser „JSESSIONID in Testform“
    MockHttpSession session = (MockHttpSession) login.getRequest().getSession(false);

    // Schritt 2: Wir gehen mit derselben Session zum geschützten Endpunkt (wie ein Browser es tun würde)
    mvc.perform(get("/api/me").session(session))
            .andExpect(status().isOk());
}

In diesem Beispiel steckt eine angenehme „lebensnahe“ Wahrheit: Wir haben uns eingeloggt, eine Session erhalten, und nun ist /api/me zugänglich, weil der Server uns über die JSESSIONID erkennt (im Test ist das die Abstraktion MockHttpSession). Ein solcher Test ist besonders nützlich, wenn der Student noch nicht ganz glaubt, dass „Session eine reale Sache und kein Mythos aus Legenden über alte Servlets ist“.

5. Erfolgloses Login in Tests

Erfolgloses Login ist nicht einfach „nun ja, passiert“. In der Sicherheit ist dies ein obligatorisches Szenario. Und es ist nicht nur für die Sicherheit wichtig, sondern auch für die Diagnose: Wenn Sie versehentlich den PasswordEncoder kaputt gemacht haben, bricht meist zuerst der Pfad der erfolglosen Authentifizierung – und das ist gut, weil Sie es durch einen Test erfahren und nicht durch einen Benutzer in Telegram.

Im stateful Form-Login endet ein erfolgloser Login standardmäßig mit einem Redirect auf /login?error. Und das ist eine völlig normale Semantik für den Browser-Flow. Unser Test sollte prüfen: Status 3xx, korrekter Redirect, und dass die Authentifizierung nicht stattgefunden hat (unauthenticated()).

import org.junit.jupiter.api.Test; 

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@Test
void loginWhenWrongPasswordThenRedirectToErrorAndUnauthenticated() throws Exception {
    // Uns interessiert genau der „erfolglose“ Pfad der Authentifizierung: falsches Passwort
    mvc.perform(formLogin().user("maria").password("wrong"))
            .andExpect(status().is3xxRedirection()) // Im Browser-Szenario ist das normalerweise ein Redirect
            .andExpect(redirectedUrl("/login?error")) // Standard-Fehler-URL beim Form-Login
            .andExpect(unauthenticated()); // Wir stellen explizit fest, dass SecurityContext nicht authentifiziert wurde
}

Beachten Sie den psychologisch wichtigen Moment: Hier prüfen wir keine Geschäfts-Endpunkte, machen keine Anfrage an /api/me, versuchen nicht „in einem Test alles zu prüfen“. Dieser Test prüft nur das Verhalten des Login-Flows bei falschem Passwort. Je kürzer und eindeutiger, desto einfacher ist er zu warten.

Manchmal ist es nützlich, ein weiteres negatives Szenario hinzuzufügen: Der Benutzer existiert nicht. Vom Verhalten her sieht das für den Client meist gleich aus (aus Sicherheitsgründen möchte man nicht mitteilen „diesen Benutzer gibt es nicht“, sonst hat man User Enumeration). Aber für Tests ist das als separater Zweig nützlich, damit Sie nicht „Benutzer nicht gefunden“ und „Passwort stimmt nicht“ verwechseln.

Manuelles POST /login ohne CSRF

Jetzt kommt eine kleine Demonstration, die sehr gut von dem Verlangen heilt, CSRF auszuschalten, „weil die Tests rot sind“. Wenn Sie versuchen, das Login manuell über MockMvc durchzuführen, aber das CSRF vergessen, erhalten Sie 403 Forbidden. Und das ist kein Fehler von Spring, sondern Spring versucht, Sie vor schlechten Entscheidungen zu bewahren.

So ein Test (oder Experiment) endet normalerweise mit 403:

import org.junit.jupiter.api.Test; // Test-Annotation
import org.springframework.http.MediaType;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@Test
void loginWhenManualPostWithoutCsrfThenForbidden() throws Exception {
    // Hier machen wir absichtlich ein „wie ein Formular“, aber ohne csrf() – um 403 zu sehen
    mvc.perform(post("/login")
            .contentType(MediaType.APPLICATION_FORM_URLENCODED) // Typischer Content-Type eines HTML-Formulars
            .param("username", "maria") // Parameter des Login-Formulars
            .param("password", "secret")) // Parameter des Passwort-Formulars
            .andExpect(status().isForbidden()); // CSRF hat Sie vor „Bequemlichkeit“ geschützt
}

Und hier ist das Wichtigste: formLogin() ist nicht nur „bequemer“, sondern hilft auch, wichtige Protokolldetails nicht zu vergessen. Daher ist es in Tests für Form-Login fast immer sinnvoll, formLogin() zu verwenden, anstatt die Anfrage manuell zusammenzustellen – es sei denn, Sie testen gezielt „was passiert ohne CSRF“.

Logout als Security-Ereignis

Beim Logout gibt es bei Anfängern oft zwei Mythen. Erster Mythos: „Logout ist, wenn wir im Frontend das Cookie gelöscht haben.“ Zweiter Mythos: „Logout ist nicht so wichtig wie Login.“ Beide Mythen verwandeln sich in der realen Welt schnell in sehr seltsame Bugs, wenn der Benutzer „ausgeloggt“ ist, aber nach einer Minute immer noch eingeloggt bleibt, weil die Session auf dem Server nirgendwo hin ist.

Im sessionbasierten Modell ist Logout eine Operation, die den Security-Zustand ändert: Spring Security muss den SecurityContext löschen, normalerweise die session invalidieren und (oft) den Browser bitten, das mit der Session verbundene Cookie (JSESSIONID) zu löschen. Deshalb ist Logout eine zustandsändernde Anfrage, und bei aktiviertem CSRF muss sie durch ein CSRF-Token geschützt sein.

Auf HTTP-Ebene sieht Logout in einer typischen Spring-Security-Konfiguration aus wie POST /logout mit CSRF. Und die Antwort ist meistens ein Redirect, z.B. auf /login?logout (das ist ein Standardmarker, den man bequem auf der Login-Seite anzeigen kann). Wenn Sie einen stateful-Zweig des Projekts testen, sollte ein Logout-Test genauso zwingend zu Ihrem Regressionstest-Set gehören wie ein Login-Test.

6. Logout: Basistests

Beginnen wir mit dem Einfachsten: Eine Logout-Anfrage mit authentifiziertem Benutzer und CSRF sollte erfolgreich sein und die erwartete Browser-Semantik (normalerweise Redirect) zurückgeben. Wir machen das explizit über POST /logout und fügen csrf() hinzu.

import org.junit.jupiter.api.Test; // Test-Annotation

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@Test
void logoutWhenAuthenticatedAndCsrfThenRedirect() throws Exception {
    mvc.perform(post("/logout")
            .with(user("maria").roles("USER")) // Geben Sie einen authentifizierten Benutzer für die Anfrage an
            .with(csrf())) // Logout ist eine zustandsändernde Operation, daher ist CSRF obligatorisch
            .andExpect(status().is3xxRedirection())
            .andExpect(redirectedUrl("/login?logout")); // Typischer „Marker“ für erfolgreiches Logout
}

Dieser Test ist nützlich, hat aber einen Nachteil: Wir haben nicht überprüft, ob der Logout wirklich „die Tür geschlossen“ hat. Immerhin könnte man den Redirect auch bei kaputter Logik zurückgeben (schlechte Idee, aber technisch möglich).

Daher die nächste Stufe – ein sehr kurzes Szenario „Login → Zugriff vorhanden → Logout → Zugriff weg“. Ja, das ist schon etwas integrativer, aber es zeigt sehr gut, dass die Session tatsächlich ungültig wird.

import org.junit.jupiter.api.Test; // Test-Annotation
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MvcResult;

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@Test
void meEndpointAfterLogoutShouldNotBeAccessible() throws Exception {
    // Schritt 1: Einloggen und eine „lebendige“ Session erhalten
    MvcResult login = mvc.perform(formLogin().user("maria").password("secret"))
            .andReturn();

    MockHttpSession session = (MockHttpSession) login.getRequest().getSession(false); // Aktuelle Session nehmen

    // Schritt 2: Prüfen, dass der Zugriff vor dem Logout besteht
    mvc.perform(get("/api/me").session(session))
            .andExpect(status().isOk());

    // Schritt 3: Logout in derselben Session + CSRF (wie ein Browser es tun würde)
    mvc.perform(post("/logout").session(session).with(csrf()))
            .andExpect(status().is3xxRedirection());

    // Schritt 4: Sicherstellen, dass dieselbe Session keinen Zugriff mehr auf die geschützte Ressource gibt
    mvc.perform(get("/api/me").session(session))
            .andExpect(status().is3xxRedirection()); // normalerweise Redirect auf /login
}

Ja, das sind etwas mehr Zeilen als die „idealen 8“. Aber dafür entsteht im Kopf des Studenten ein vollständiges Bild: Die Session lebt, solange wir eingeloggt sind; Logout setzt den Zustand zurück; danach gewährt dieselbe Session keinen Zugriff mehr. Das ist genau der Test, der vor „ich habe wohl Logout hinzugefügt, aber er macht nichts“ bewahrt.

7. Anpassung des Login-Flows

Fast unvermeidlich kommt der Moment, in dem Sie nicht mehr in der „Standardwelt“ leben und der Login-Flow benutzerdefiniert wird. Zum Beispiel ändern Sie die Login-Processing-URL (nicht /login, sondern etwas wie /api/auth/login-form), oder Sie entscheiden, dass Sie sich per E-Mail einloggen, und dann ist der Formularparameter nicht mehr username.

Und hier gibt es eine wichtige Regel des Erwachsenwerdens: Wenn Sie den Vertrag des Login-Flows geändert haben, müssen sich die Tests zusammen mit dem Vertrag ändern. Das ist nicht „Tests behindern die Entwicklung“, sondern Tests halten die Realität fest, die Sie selbst erschaffen.

Beispiel: Sie haben Form-Login beibehalten, aber die Login-Processing-URL geändert. Dann müssen Sie im Test die URL beim Aufbau der Anfrage angeben:

import org.junit.jupiter.api.Test; // Test-Annotation

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@Test
void loginWhenCustomLoginUrlThenStillWorks() throws Exception {
    // Wenn Sie loginProcessingUrl in der Konfiguration geändert haben – muss der Test die neue URL verwenden
    mvc.perform(formLogin("/api/auth/login-form")
            .user("maria")
            .password("secret"))
            .andExpect(status().is3xxRedirection()); // Zumindest das erfolgreiche Login prüfen
}

Und wenn Sie den Parameternamen für das Login geändert haben, wird auch das abgebildet:

import org.junit.jupiter.api.Test; // Test-Annotation

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@Test
void loginWhenEmailParameterThenOk() throws Exception {
    // userParameter(...) muss dem entsprechen, was Sie tatsächlich im Authentifizierungsfilter erwarten
    mvc.perform(formLogin()
            .userParameter("email") // Jetzt loggen wir uns nicht per username, sondern per email ein
            .user("maria@example.com")
            .password("secret"))
            .andExpect(status().is3xxRedirection()); // In den ersten Schritten fixieren wir „es authentifiziert“
}

Hier prüfe ich bewusst nicht die Redirect-URL – weil Sie bei Anpassung auch den Success Handler geändert haben könnten. In den ersten Schritten ist es nützlicher, „es authentifiziert überhaupt“ zu fixieren und dann den Test strenger zu machen.

Und noch ein praktischer Tipp: Anpassungen sollten schrittweise erfolgen. Erst URL geändert – Test korrigiert. Dann Parameter geändert – Test korrigiert. Und erst wenn alles grün ist, kann man eine schöne Architektur „unser Login-Flow ist wie in einem großen Produkt“ entwerfen. Sonst bekommen Sie ein Produkt wie in einem großen Produkt, aber irgendwie ohne Login.

8. Typische Fehler bei formLogin() und Logout

Fehler Nr. 1: Versuchen, Login über user() zu testen und das als „Login-Prüfung“ zu betrachten.
user() ist ein hervorragendes Werkzeug, um Zugriffsregeln zu prüfen, aber es prüft überhaupt nicht, ob Ihr DaoAuthenticationProvider funktioniert, ob das Passwort richtig codiert wird, ob Ihr UserDetailsService den Benutzer wirklich findet. Wenn Sie nicht mindestens ein paar Tests mit formLogin() hinzugefügt haben, testen Sie faktisch den Login-Flow nicht.

Fehler Nr. 2: 200 OK von erfolgreichem Form-Login erwarten.
Im browserorientierten Szenario endet erfolgreiches Login normalerweise mit 302 und einem Redirect. Wenn Sie im Test 200 erwarten, testen Sie entweder das Falsche oder sind zufällig in ein anderes Verhalten geraten (z.B. ein benutzerdefinierter Success Handler). Besser ist es, mit der Erwartung von 3xx und authenticated() zu beginnen und dann das Verhalten an Ihren konkreten Vertrag anzupassen.

Fehler Nr. 3: Vergessen, dass formLogin() einen echten Benutzer im User Store erfordert.
Tests mit @WithMockUser funktionieren auch ohne Datenbank und ohne In-Memory-Benutzer hervorragend. Aber formLogin() nicht. Es authentifiziert ehrlich. Daher müssen Sie entweder einen vorbereiteten Benutzer in der Testdatenbank/dem Seed haben, oder eine separate Testkonfiguration des User Stores, oder diese Tests auf einem entsprechenden Checkpoint des Projekts ausführen.

Fehler Nr. 4: Logout als GET /logout oder als POST ohne CSRF testen.
Bei aktiviertem CSRF muss Logout eine zustandsändernde Anfrage mit Token sein. Fehlt das Token, ist 403 die normale Antwort. Und das „behindert die Entwicklung“ nicht, sondern Security macht ihre Arbeit. In Tests ist es besser, explizit zu sein: post("/logout").with(csrf()), um nicht zufällige Unterschiede im Verhalten zwischen Konfigurationen zu fangen.

Fehler Nr. 5: In einem Test Login/Logout und gleich alle Geschäfts-Endpunkte des Projekts mischen.
Manchmal möchte man einen einzigen „riesigen“ Test schreiben: Login, Profil, Entwürfe, Publish, Logout … Ein solcher Test wird „unerklärlicherweise“ fehlschlagen und zu Ihrer persönlichen Serie mit 12 Staffeln werden. Besser ist es, Login/Logout-Tests getrennt und kurz zu halten und Geschäfts-Endpunkte mit user()-Ansätzen zu prüfen – so bleibt der Test-Set wartbar und vorhersagbar.

1
Aufgabe
Spring Security, Level 27, Lektion 1
Gesperrt
FormLogin mit erfolgreichem und fehlgeschlagenem Login
FormLogin mit erfolgreichem und fehlgeschlagenem Login
1
Aufgabe
Spring Security, Level 27, Lektion 1
Gesperrt
Logout und erneute Nutzung derselben Session
Logout und erneute Nutzung derselben Session
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION