enhancement
major
minor
defect
critical
major
minor
major
#30110
Abgebrochener externer Login (OIDC) hinterlässt eine angemeldete Session ohne Tokens
Beobachtung
Im Betrieb von easy-planning (TopLogic 7.10.x, Login über OIDC/pac4j) sind an einem Vormittag etwa elf Benutzer in Sessions gelandet, in denen sie normal arbeiten konnten, die aber keine UserTokens hatten. Alles, was die Tokens braucht, scheiterte danach dauerhaft, z. B. der 3D-Viewer aus tl-uber3d mit einer NullPointerException bei jedem Rendern (dort inzwischen abgefangen, tl-uber-3d Issue #117, Releases 2.0.6 und 2.1.3).
Im Log treten alle betroffenen Sessions zusammen mit einem gescheiterten Login auf:
ERROR ExternalAuthenticationServlet - Login failed: The user with the loginName '…' had passed all checks and was about to be logged in. But an error occurred! … Caused by: … Commit aborted in response to long running concurrent refetch at … UserMonitor.login(UserMonitor.java:…) at … SessionService.sendEvent(…) at … SessionService.getNewSessionForUser(…) at … Login.loginFromExternalAuth(…) at … ExternalAuthenticationServlet.loginUser(…)
Wenige Minuten später folgt für denselben Benutzer „Reusing an existing session for user '…'.“, und der Benutzer arbeitet ohne Tokens weiter.
Ursache
- SessionService.getNewSessionForUser legt die HTTP-Session an, bindet den Benutzer daran (installSession) und registriert sie (putSession). Erst danach geht das Event LOGGED_IN an die Listener. Der Listener, der die Login-Statistik schreibt (TL 7: UserMonitor, TL 8: StoreUserEventListener, standardmäßig konfiguriert), committet dabei im Thread des Logins.
- SessionService.sendEvent ruft die Listener ohne Fehlerbehandlung auf. Scheitert der Commit (hier: Refetch-Stau in der Datenbank), wirft die Exception bis in den Login durch. Die bereits angemeldete Session wird nicht abgeräumt.
- Pac4jAuthenticationServlet.loginUser installiert die OIDC-Tokens erst nach super.loginUser(…). Nach der Exception bleibt die Session ohne Tokens.
- Mit reuseSession leitet ExternalAuthenticationServlet beim nächsten Aufruf auf die vorhandene Session weiter. TL 8 prüft dabei zusätzlich isAuthenticatedSession, also nur, ob ein nicht-anonymer Benutzer an der Session hängt. Das trifft auf die halbe Session zu.
TL 8 ist genauso betroffen (geprüft: 8.0.0-alpha11 und aktueller master). Dort kommt der Login in eine bestehende Session hinzu (SessionService.login mit vorhandener Registrierung, z. B. die anonyme Session der React-Oberfläche): Auch dort sind Benutzer, Session-ID und Registrierung schon umgestellt, bevor LOGGED_IN committet wird.
Lösung
- SessionService.sendEvent fängt Fehler eines UserEventListener ab und loggt sie. Die übrigen Listener werden weiter benachrichtigt, Login und Logout laufen zu Ende. Die Login-Statistik ist eine Nebenwirkung und darf einen Login nicht verhindern. Das allein hätte den Vorfall verhindert.
- ExternalAuthenticationServlet beendet die Session, wenn der Login scheitert, nachdem sie bereits für den Benutzer angemeldet wurde. Damit kann eine halb angemeldete Session nicht über reuseSession weiterverwendet werden, auch nicht bei anderen Fehlern an dieser Stelle (z. B. beim Installieren der Tokens).
Betrifft TL 7 (Backport auf TL/TL_7.12_x) und TL 8 (master).
Der Auslöser selbst, die lang laufenden Refetches im Betrieb von easy-planning, ist ein eigenes Thema und nicht Teil dieses Tickets.