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