Authentifizierung fuer Modul jdk.httpserver
ulrich
2021-06-02 6e87f899f1f811fbbc88d70a678b20ebe5c3ae83
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
  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 <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;
  }
  
  
  
  
 
}