Neon [1] ist ein ultrakompakter, modular aufgebauter HTTP-Server auf der Grundlage des Java-Moduls jdk.httpserver
zum Einbetten in Apps und Microservices. In diesem Dokument ist beschrieben, wie die Erweiterung zur Authentifizierung neon-auth
eingesetzt wird.
Authentifizierung
Authentifizierung ist der Nachweis der Indentität eines Menschen, eines Geräts, einem Dokument oder einer Information [3].
Im Rahmen der Authentifizierung wird geprüft, ob eine Anfrage von einem authentifizierten Nutzer kommt. Es wird nicht geprüft, ob der Nutzer autorisiert ist, die Anfrage auszuführen.
Neon [1] implementiert die Authentifizierung mit Hilfe eines Authenticator
. Abgeleitet von com.sun.net.httpserver.Authenticator
werden mit Verwendung eines Authenticators alle HTTP-Anfragen nur verarbeitet, wenn sie von authentifizierten Nutzern stammen. Das Modul neon-auth
erweitert Neon [1] hierbei um eine Möglichkeit der Authentifizierung nach RFC 6750 Bearer Token Authentication [5].
In diesem Dokument ist die Verwendung des Moduls neon-auth
auf der Seite des Servers beschrieben. Im Dokument Authentifizierung für Webclients [10] wird zudem betrachtet, wie die Authentifizierung auf der Seite des Clients implementiert werden kann.
Authentifizierung einschalten
Zum Einschalten der Authentifizierung für einen bestimmten HTTP-Service-Endpunkt kann die Klasse BearerAuthenticator
dienen. Sollen ein oder mehrere HTTP-Kontexte eine Authentifizierung erfordern, wird in der Serverbeschreibung [4] ein Eintrag für den Authenticator angelegt.
"authenticator": {
"className": "de.uhilger.neon.auth.BearerAuthenticator",
"attributes": {
"loginTL": 10000,
"expireSeconds": 7200,
"refreshExpireSeconds": 86400,
"refreshSeconds": 3600,
"loginRoute": "/login",
"refreshRoute": "/refresh",
"authenticatorName": "myAuth"
}
}
Das Element authenticator
wird nur einmal in einer Serverbeschreibung angegeben und besitzt die folgenden Eigenschaften.
- className
-
Die Klasse des Authenticators
- loginTL
-
Die Wartezeit in Millisekunden zwischen zwei beliebigen Logins. Nach jedem Aufruf eines Login werden weitere Login-Versuche über alle Nutzer hinweg für die hier angegebene Wartezeit pausiert. Dies bremst Brute Force Attacken.
- expireSeconds
-
Die Gültigkeit eines Authentifizerungs-Token in Sekunden
- refreshExpireSeconds
-
Die Gültigkeit eines Refresh-Tokens in Sekunden
- refreshSeconds
-
Die Anzahl von Sekunden, bis ein Authentifizierungs-Token zum Refresh aufgefordert wird.
- loginRoute
-
Die Route, die für die Anmeldung über diesen Authenticator genutzt wird. Aufrufe dieser Route sind zur Bindung an einen
BearerLoginService
vorgesehen und werden vom Authenticator ohne gültigen Authentifizierungs-Token zur Verarbeitung weitergegeben.
- refreshRoute
-
Die Route, die für die Erneuerung eines Authentifizierungs-Token genutzt wird. Aufrufe dieser Route sind zur Bindung an einen
BearerRefreshService
vorgesehen und werden vom Authenticator ohne gültigen Authentifizierungs-Token zur Verarbeitung weitergegeben. - authenticatorName
-
Der Name, über den der Authenticator an HTTP-Kontexte gebunden wird.
HTTP-Kontexte, für deren Nutzung eine Authentifizierung vorgesehen ist, erhalten in der Serverbeschreibung [4] einen Verweis auf diesen Authenticator.
"server": [
{
"name": "External Server",
"port":7000,
"contexts": [
{
"className": "de.uhilger.neon.Handler",
"sharedHandler": "true",
"contextPath": "/pfad/zum/kontext",
"authenticator": "myAuth",
"attributes": {
"contextName": "admin"
}
}
]
}
]
Die Eigenschaft "authenticator": "myAuth"
im Element contexts
verweist auf die zuvor dargestellte Konfiguration des Authenticators und bewirkt, dass alle Anfragen, die an diesen HTTP-Kontext gerichtet sind, nur nach erfolgreicher Authentifizierung gelingen.
Benutzerverzeichnis
Ein beliebig beschaffenes Benutzerverzeichnis enthält in der Regel die Angaben über Bereiche, die eine Authentifizierung erfordern, autorisierte Benutzer und deren Rechte. Für die Verwendung von neon-auth
ist es vorgesehen, über die Schnittstelle Directory
derartige Benutzerverzeichnisse in den Ablauf der Authentifizierung einzubinden.
Directory
public interface Directory {
public boolean isValid(String userId, String password);
public boolean hasRole(String userId, String roleId);
}
Mit der Klasse SimpleDirectory
ist eine sehr einfache Implementierung eines solchen Benutzerverzeichnisses enthalten, dessen Inhalt aus einer Datei gelesen wird.
SimpleDirectory
SimpleDirectory directory = new SimpleDirectory();
directory.readFromFile(new File("conf", "my.directory"));
directory.setName("/meine-app");
In der Datei my.directory
erwartet die Klasse SimpleDirectory
Einträge in folgender Struktur.
SimpleDirectory
# Benutzer meiner App
# [userId]=[password],[roleId],[roleId],etc.
test=test,testRolle
ulrich=geheim,testRolle,andereRolle
fred=undisclosed,nutzerRolle,adminRolle
Der Ausdruck vor dem Gleichheitszeichen ist die Nutzer-Kennung, die zur Anmeldung dient. Der erste Eintrag nach dem Gleichheitszeichen ist das Kennwort, gefolgt von einer Liste mit Komma getrennter Rollen-IDs. Eine Autorisierung auf der Grundlage von Rollen kann mit diesen Angaben erfolgen, ist aber nicht Teil der Implementierung von neon-auth
.
neon-auth
liefert mit der Schnittstelle Directory
und dem Beispiel SimpleDirectory
den Ausgangspunkt für andere Implementierungen wie beispielsweise gegen eine Datenbank oder einen Web-Service. Erweiterungen oder Abwandlungen der Klasse SimpleDirectory
sollten eine Form der Verschlüsselung für das Kennwort einbauen, damit es nicht im Klartext für andere lesbar vorliegt.
Login
Ist ein HTTP-Kontext mit der Klasse BearerAuthenticator
verbunden, wird gemäß Spezifikation [5] eine Antwort mit Status 401 Unauthorized
gesendet, wenn eine HTTP-Anfrage ohne Authentifizerung für diese Ressource eingeht.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"
Der Client muss dann einen HTTP-Service-Endpunkt aufrufen, der eine Authentifizierung ermöglicht. Ein solcher Endpunkt zum Login kann mit der Klasse BearerLoginService
mit Hilfe eines Actors wie in folgendem Beispiel implementiert werden.
@Actor(name = "loginActor")
public class LoginActor extends ConsumerActor {
@Action(handler = {"admin"}, route = "/login", type = Action.Type.POST, handlesResponse = true)
public void run(HttpExchange exchange) throws IOException {
Object o = getData("user.directory");
if (o instanceof Directory) {
new BearerLoginService((Directory) o).login(exchange);
}
}
}
Ein solcher Login-Actor muss einem HTTP-Kontext zugeordnet sein, der eine Authentifizierung erfordert und dem zu diesem Zweck ein BearerAuthenticator
zugeordnet wurde.
- Wichtig
-
Der Eintrag
route="/login"
derAction
-Annotation muss mit dem Eintrag im AttributloginRoute
der Konfiguration des Authenticators übereinstimmen.
Über /pfad/zum/kontext/login
kann eine Benutzeranmeldung erfolgen. Der BearerLoginService
erwartet den Aufruf per HTTP POST
mit {"name": "fred", "password": "secret"}
im Body der Anfrage. Ist die Anmeldung erfolgreich, sendet der BearerLoginService
die Antwort selbsttätig gemäß der Bearer Token Spezifikation [5]. Deshalb muss der Actor handlesResponse=true
setzen und als Parameter den HttpExchange
verlangen wie im Beispiel gezeigt.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"mF_9.B5f-4.1JqM",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
Ein Authenticator kann auf diese Weise an mehrere HTTP-Kontexte gebunden sein, woraufhin diese jeweils eine Authentifizierung verlangen. Es genügt, einen der betreffenden HTTP-Kontexte mit einem Actor für das Login zu versehen. Auf diese Weise durchgeführte Anmeldungen gelten für alle HTTP-Kontexte, die diesen Authenticator verwenden.
Der BearerLoginService
überprüft Nutzerangaben gegen ein Benutzerverzeichnis, das die Schnittstelle Directory
implementiert. Eine Anwendung muss selbst dafür sorgen, ein solches Benutzerverzeichnis bereitzuhalten. Im Beispiel ist dies mit der Methode getData
der Basisklasse ConsumerActor
[9] realisiert, die die Schnittstelle DataProvider
[8] implementiert.
Refresh
Ist ein Authentifizierungs-Token abgelaufen, erhält der Client bei Anfrage einer Ressource, die eine Authentifizierung erfordert, eine Ablehnung.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
error="invalid_token",
error_description="The access token expired"
Bei der Bearer Token Authentication muss der Client den Refresh Token aus der letzten Authentifizierung verwenden, um die Authentifizierung zu erneuern, was dem Benutzer während einer längeren Sitzung erspart, sein Kennwort erneut einzugeben.
Zum Erneuern der Authentifizierung muss der Client einen HTTP-Service-Endpunkt aufrufen, der die Erneuerung der Authentifizierung ermöglicht. Ein solcher Endpunkt kann mit der Klasse BearerRefreshService
mit Hilfe eines Actors wie in folgendem Beispiel implementiert werden.
@Actor(name = "refreshActor")
public class RefreshActor {
@Action(handler = {"admin"}, route = "/refresh", type = Action.Type.POST, handlesResponse = true)
public void run(HttpExchange exchange) throws IOException {
new BearerRefreshService().refresh(exchange);
}
}
Ein solcher Refresh-Actor muss einem HTTP-Kontext zugeordnet sein, der eine Authentifizierung erfordert und dem zu diesem Zweck ein BearerAuthenticator
zugeordnet wurde.
- Wichtig
-
Der Eintrag
route="/refresh"
derAction
-Annotation muss mit dem Eintrag im AttributrefreshRoute
der Konfiguration des Authenticators übereinstimmen.
Über /pfad/zum/kontext/refresh
kann eine Erneuerung der Authentifizierung erfolgen. Der BearerRefreshService
erwartet darüber eine HTTP-Anfrage im spezifikationsgemäßen Format [5] wie folgt:
POST /meine-app/refresh HTTP/1.1
Host: example.com
grant_type=refresh_token
&refresh_token=xxxxxxxxxxx
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx
Ist der Refresh Token gültig, antwortet der BearerRefreshService
so wie beim Login mit einem neuen Satz Tokens.
Verweise
Änderungsverlauf
- Version 1
-
Vom 18. Juni 2021
http-base - Dateien und Streams ausliefern
http-realm - Nutzerverzeichnis zur Authentifizierung
http-oauth - Bearer Authentication für Neon
http-up - Dateien zu Neon heraufladen
http-adoc - Asciidoctor mit Neon transformieren
http-image - Bilder mit Neon verwenden - Version 2
-
Vom 2. Januar 2022
http-cm - Dateien mit Neon verwalten
http-template - Mustache-Vorlagen mit Neon verarbeiten - Version 3
-
Vom 20. Februar 2024
Authentifizierung - Bearer Token Authentication für Neon 2