/* jwtTest - JSON Web Token Testimplementierung Copyright (C) 2021 Ulrich Hilger This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package de.uhilger.httpserver.auth.handler; import com.sun.net.httpserver.Authenticator; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import de.uhilger.httpserver.auth.FormAuthenticator; import de.uhilger.httpserver.auth.TokenAuthenticator; import de.uhilger.httpserver.auth.realm.User; import de.uhilger.httpserver.base.handler.HttpResponder; import java.io.IOException; import java.util.HashMap; import java.util.logging.Logger; /** * Eine Login-Anfrage entgegennehmen und im Falle der erfolgreichen * Anmeldung weiterleiten zur urspruenglich angefragten Ressource. * * Dieser Handler nimmt einen HTTP POST entgegen und erwartet die * Benutzer-ID und das Kennwort im Body der Anfrage in der Form, wie sie von * einem HTML-Formular gesendet wird, dessen Eingabefelder wie folgt angelegt * sind: * *
 * 
* * * *
*
* * Mit einem so angelegten HTML-Formular kommt die Anfrage vom Formular als * Body der HTTP POST Anfrage in folgender Form beim Server an: * *
 * j_username=nutzerid&j_password=nutzerkennwort
 * 
* * Zur Authentifizierung zieht der LoginHandler einen JWTAuthenticator heran. * Der LoginHandler erwartet zu diesem Zweck ein Attribut namens * "jwtauth" im HttpContext des HttpExchange-Objekts, das auf ein Objekt der * Klasse JWTAuthenticator verweist, * * Bei erfolgreicher Authentifizierung ermittelt der LoginHandler die * urspruenglich angefragte Ressource mit Hilfe des Authenticators und * antwortet dem Client mit einer Weiterleitung an diese Ressource * (HTTP Statuscode 303 "See Other"). * * * * --- * Der nachfolgende Text ist veraltet. * * Die ursprunglich angefragte Ressource wird mit Hilfe eines Cookie namens * JWTAuthenticator.JWT_INDICATOR * ermittelt, der vor dem Aufruf des Login-Formulars vom JWTAuthenticator * erzeugt wird. * * * Diesen * JWTAuthenticator verwendet der LoginHandler, um den Benutzer anzumelden. * Mit erfolgreicher Anmeldung gibt der JWTAuthenticator einen JSON Web Token * an den LoginHandler ab, den dieser als HttpOnly Cookie an den Browser * weiterreicht. Nachfolgende Anfragen erfolgen dann mit diesem JWT-Cookie * und werden vom JWTAuthenticator durchgelassen. * * Die Antwort vom LoginHandler muss in der Client-App nicht verarbeitet werden, * weil der Browser ohne sonstiges zutun nachfolgenden Anfragen den JWT als * Cookie mitgibt. * * JWT muessen als HttpOnly Cookie an den Browser ausgegeben werden, weil * anderenfalls per cross site scripting (XSS) im Browser auf den JWT * zugegriffen werden kann. Die zusaetzliche Option Secure bewirkt, dass * der JWT nur an den Server uebermittelt wird, wenn die Verbindung * verschluesselt ist. Auf diese Weise kann bei einer Man-in-the-middle-Attacke * der JWT nicht ausgelesen werden. * * Die Verwendung von Cookies fuer JWT hat zudem den Vorteil, dass die * Client-App keine Logik zur Verwendung des JWT besitzen muss. Der Browser * tauscht selbsttaetig den JWT mit dem Server aus. * * Abwandlungen dieses LoginHandlers koennen z.B. eine JSON-Notation * fuer Benutzer-ID und Kennwort im Body verwenden. * * HTTP POST /jwttest/login mit Benutzername und Kennwort im Body: * {"name": "fred", "password": "secret"} * * Das kann fuer API-Authentifizierungen nutzlich sein. Dazu passend wird auf * dem Client wie folgt vorgegangen. * * Benutzername und Kennwort als HTTP POST an /jwttest/login senden * var sendObject = JSON.stringify({name: user, password: password}); * * * @author Ulrich Hilger * @version 1, 21. Mai 2021 */ public class FormLoginHandler extends LoginHandler { private static final Logger logger = Logger.getLogger(FormLoginHandler.class.getName()); public static final String FORM_NUTZER_NAME = "j_username"; public static final String FORM_KENNWORT = "j_password"; public static final String HEADER_LOCATION = "Location"; public static final String RESP_SEE_OTHER = "See Other"; public FormLoginHandler() { //this.ctx = ctx; } @Override protected void loginResponse(HttpExchange exchange, Authenticator auth, String token) throws IOException { if(auth instanceof FormAuthenticator) { setAuthenticatedHeader(exchange, auth, token); FormAuthenticator formAuth = (FormAuthenticator) auth; // Weiterleitungsziel bestimmen und in den Antwort-Kopf schreiben String wert = formAuth.cookieLesen(exchange, TokenAuthenticator.JWT_INDICATOR); logger.info(wert); String sessionReferer = formAuth.getReferer(wert); Headers respHeaders = exchange.getResponseHeaders(); respHeaders.add(HEADER_LOCATION, sessionReferer); // Antwort senden // https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections // 303 See Other erlaubt eine Weiterleitung // mit Wechsel von POST (login) zu GET (urspruengliche Ressource) HttpResponder responder = new HttpResponder(); responder.antwortSenden(exchange, 303, RESP_SEE_OTHER); } else { throw new IOException("Wrong authenticator type, should have been FormAuthenticator."); } } @Override protected User getUser(HttpExchange exchange) throws IOException { String body = bodyLesen(exchange); String[] nameWertPaare = body.split(TokenAuthenticator.STR_AMP); HashMap werte = new HashMap(); for (String nameWert : nameWertPaare) { String[] teile = nameWert.split(TokenAuthenticator.STR_EQUAL); werte.put(teile[0], teile[1]); } User nutzer = new User(); nutzer.setName(werte.get(FORM_NUTZER_NAME)); nutzer.setPassword(werte.get(FORM_KENNWORT)); return nutzer; } }