Authentifizierung fuer Modul jdk.httpserver
ulrich
2021-06-02 6d44a4304440a7a0951d2ee5c4c8173716125e0f
commit | author | age
9ee357 1 /*
U 2   jwtTest - JSON Web Token Testimplementierung 
3   Copyright (C) 2021  Ulrich Hilger
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU Affero General Public License as
7   published by the Free Software Foundation, either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 package de.uhilger.httpserver.auth.handler;
19
20 import com.sun.net.httpserver.Authenticator;
21 import com.sun.net.httpserver.Headers;
22 import com.sun.net.httpserver.HttpExchange;
23 import de.uhilger.httpserver.auth.FormAuthenticator;
24 import de.uhilger.httpserver.auth.TokenAuthenticator;
25 import de.uhilger.httpserver.auth.realm.User;
6e87f8 26 import de.uhilger.httpserver.base.handler.HttpHelper;
9ee357 27 import de.uhilger.httpserver.base.handler.HttpResponder;
U 28 import java.io.IOException;
29 import java.util.HashMap;
30 import java.util.logging.Logger;
31
32 /**
33  * Eine Login-Anfrage entgegennehmen und im Falle der erfolgreichen 
34  * Anmeldung weiterleiten zur urspruenglich angefragten Ressource.
35  * 
36  * Dieser Handler nimmt einen HTTP POST entgegen und erwartet die 
37  * Benutzer-ID und das Kennwort im Body der Anfrage in der Form, wie sie von 
38  * einem HTML-Formular gesendet wird, dessen Eingabefelder wie folgt angelegt 
39  * sind:
40  * 
41  * <pre>
42  * <form method="POST" action="/login">
43  *   <input  name="j_username" type="text">
44  *   <input name="j_password" type="password">
45  *   <button type="submit">Login</button>
46  * </form>
47  * </pre>
48  *
49  * Mit einem so angelegten HTML-Formular kommt die Anfrage vom Formular als 
50  * Body der HTTP POST Anfrage in folgender Form beim Server an:
51  * 
52  * <pre>
53  * j_username=nutzerid&j_password=nutzerkennwort
54  * </pre>
55  * 
56  * Zur Authentifizierung zieht der LoginHandler einen JWTAuthenticator heran.
57  * Der LoginHandler erwartet zu diesem Zweck ein Attribut namens 
58  * "jwtauth" im HttpContext des HttpExchange-Objekts, das auf ein Objekt der 
59  * Klasse JWTAuthenticator verweist,
60  * 
61  * Bei erfolgreicher Authentifizierung ermittelt der LoginHandler die 
62  * urspruenglich angefragte Ressource mit Hilfe des Authenticators und 
63  * antwortet dem Client mit einer Weiterleitung an diese Ressource 
64  * (HTTP Statuscode 303 "See Other").
65  * 
66  * 
67  * 
68  * ---
69  * Der nachfolgende Text ist veraltet.
70  * 
71  * Die ursprunglich angefragte Ressource wird mit Hilfe eines Cookie namens 
72  * <code>JWTAuthenticator.JWT_INDICATOR</code>
73  * ermittelt, der vor dem Aufruf des Login-Formulars vom JWTAuthenticator 
74  * erzeugt wird.
75  * 
76  * 
77  * Diesen 
78  * JWTAuthenticator verwendet der LoginHandler, um den Benutzer anzumelden. 
79  * Mit erfolgreicher Anmeldung gibt der JWTAuthenticator einen JSON Web Token 
80  * an den LoginHandler ab, den dieser als HttpOnly Cookie an den Browser 
81  * weiterreicht. Nachfolgende Anfragen erfolgen dann mit diesem JWT-Cookie 
82  * und werden vom JWTAuthenticator durchgelassen.
83  * 
84  * Die Antwort vom LoginHandler muss in der Client-App nicht verarbeitet werden, 
85  * weil der Browser ohne sonstiges zutun nachfolgenden Anfragen den JWT als 
86  * Cookie mitgibt. 
87  * 
88  * JWT muessen als HttpOnly Cookie an den Browser ausgegeben werden, weil 
89  * anderenfalls per cross site scripting (XSS) im Browser auf den JWT 
90  * zugegriffen werden kann. Die zusaetzliche Option Secure bewirkt, dass 
91  * der JWT nur an den Server uebermittelt wird, wenn die Verbindung 
92  * verschluesselt ist. Auf diese Weise kann bei einer Man-in-the-middle-Attacke 
93  * der JWT nicht ausgelesen werden.
94  * 
95  * Die Verwendung von Cookies fuer JWT hat zudem den Vorteil, dass die 
96  * Client-App keine Logik zur Verwendung des JWT besitzen muss. Der Browser 
97  * tauscht selbsttaetig den JWT mit dem Server aus.
98  * 
99  * Abwandlungen dieses LoginHandlers koennen z.B. eine JSON-Notation 
100  * fuer Benutzer-ID und Kennwort im Body verwenden.
101  * 
102  * HTTP POST /jwttest/login mit Benutzername und Kennwort im Body:
103  * {"name": "fred", "password": "secret"}
104  * 
105  * Das kann fuer API-Authentifizierungen nutzlich sein. Dazu passend wird auf 
106  * dem Client wie folgt vorgegangen.
107  * 
108  * Benutzername und Kennwort als HTTP POST an /jwttest/login senden
109  * var sendObject = JSON.stringify({name: user, password: password});
110  * 
111  * 
112  * @author Ulrich Hilger
113  * @version 1, 21. Mai 2021
114  */
115 public class FormLoginHandler extends LoginHandler {
116   
117   private static final Logger logger = Logger.getLogger(FormLoginHandler.class.getName());
118   
119   
120   public static final String FORM_NUTZER_NAME = "j_username";
121   public static final String FORM_KENNWORT = "j_password";
122   
123   public static final String HEADER_LOCATION = "Location";
124   
125   public static final String RESP_SEE_OTHER = "See Other";
126   
127   
128   public FormLoginHandler() {
129     //this.ctx = ctx;
130   }
131
132   @Override
133   protected void loginResponse(HttpExchange exchange, Authenticator auth, String token) throws IOException {
134     if(auth instanceof FormAuthenticator) {
135       setAuthenticatedHeader(exchange, auth, token);
136       FormAuthenticator formAuth = (FormAuthenticator) auth;
137       // Weiterleitungsziel bestimmen und in den Antwort-Kopf schreiben
138       String wert = formAuth.cookieLesen(exchange, TokenAuthenticator.JWT_INDICATOR);
139       logger.info(wert);
140       String sessionReferer = formAuth.getReferer(wert);
141       Headers respHeaders = exchange.getResponseHeaders();
142       respHeaders.add(HEADER_LOCATION, sessionReferer);
143       // Antwort senden
144       // https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
145       // 303 See Other erlaubt eine Weiterleitung
146       // mit Wechsel von POST (login) zu GET (urspruengliche Ressource)
147       HttpResponder responder = new HttpResponder();
148       responder.antwortSenden(exchange, 303, RESP_SEE_OTHER);
149     } else {
150       throw new IOException("Wrong authenticator type, should have been FormAuthenticator.");
151     }
152   }
153
154   @Override
155   protected User getUser(HttpExchange exchange) throws IOException {
6e87f8 156     String body = new HttpHelper().bodyLesen(exchange);
9ee357 157     String[] nameWertPaare = body.split(TokenAuthenticator.STR_AMP);
U 158     HashMap<String, String> werte = new HashMap();
159     for (String nameWert : nameWertPaare) {
160       String[] teile = nameWert.split(TokenAuthenticator.STR_EQUAL);
161       werte.put(teile[0], teile[1]);
162     }
163     User nutzer = new User();
164     nutzer.setName(werte.get(FORM_NUTZER_NAME));
165     nutzer.setPassword(werte.get(FORM_KENNWORT));
166
167     return nutzer;
168   }
169   
170   
171   
172   
173
174 }