/*
|
http-auth - Authentication Extensions to jdk.httpserver
|
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 <https://www.gnu.org/licenses/>.
|
*/
|
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.HttpHelper;
|
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:
|
*
|
* <pre>
|
* <form method="POST" action="/login">
|
* <input name="j_username" type="text">
|
* <input name="j_password" type="password">
|
* <button type="submit">Login</button>
|
* </form>
|
* </pre>
|
*
|
* Mit einem so angelegten HTML-Formular kommt die Anfrage vom Formular als
|
* Body der HTTP POST Anfrage in folgender Form beim Server an:
|
*
|
* <pre>
|
* j_username=nutzerid&j_password=nutzerkennwort
|
* </pre>
|
*
|
* 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
|
* <code>JWTAuthenticator.JWT_INDICATOR</code>
|
* 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 = new HttpHelper().bodyLesen(exchange);
|
String[] nameWertPaare = body.split(TokenAuthenticator.STR_AMP);
|
HashMap<String, String> 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;
|
}
|
|
|
|
|
|
}
|