Basic-Authentication für Single-User-Applikation mit Quarkus

Wir haben eine simple Single-User-Applikation mit Quarkus geschrieben und möchten dieser nun für den Admin-Bereich einen Zugriffsschutz hinzufügen. Es reicht uns vollkommen aus Basic Authentication zu nutzen und die Benutzerdaten embedded (aber konfigurierbar) zu speichern.
Diese Aufgabe ist eigentlich schnell erledigt, aber leider etwas wirr bzw. umständlich dokumentiert [2].
Konfiguration
Die komplette Arbeit können wir in unserer application.properties erledigen:
quarkus.http.auth.basic=true
quarkus.http.auth.policy.admin-policy.roles-allowed=admin
quarkus.http.auth.permission.admin-permission.paths=/admin*
quarkus.http.auth.permission.admin-permission.policy=admin-policy
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.admin=password
quarkus.security.users.embedded.roles.admin=admin
Gehen wir die Konfiguration Zeile für Zeile durch:
- Basic Authentication aktivieren
- Policy mit dem Namen
admin-policyerstellen zu der alle Benutzer mit der Rolleadmingehören - Permission namens
admin-permissionerstellen und den Pfad/admin*zuweisen (das*dient hier als Wildcard. Der Pfad matched also sowohl auf/adminals bspw. auch auf/admin/foo/bar) - Zuvor erstellte
admin-permissiondie Policyadmin-policyhinzufügen - Embedded Benutzerverwaltung aktivieren
- Klartextpasswörter aktivieren (ansonsten werden die Passwörter in dem Format
HEX( MD5( username ":" realm ":" password ) )erwartet - Benutzer
adminmit dem Passwortpassworderstellen - Benutzer
admindie Rolleadminzuweisen
Dependencies
Nun müssen wir nur noch die auf WildFly Elytron basierte Quarkus-Extension unserer pom.xml als Abhängigkeit hinzufügen
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>
Resources
Erstellen wir nun noch zwei Resources bzw. Endpoints um unserer Setup zu testen.
@Path("/admin")
public class AdminResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello admin";
}
}
@Path("/public")
public class PublicResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello user";
}
}
Der /admin-Endpoint dürfte nur für den admin-Benutzer zugreifbar sein und /public für jeden - auch unauthentifizierten - Benutzer. Testen wir das einfach.
Tests
@QuarkusTest
public class AdminResourceTest {
@Test
public void testAdminEndpoint() {
given()
.when()
.auth().basic("admin", "password")
.get("/admin")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.body(is("hello admin"));
}
@Test
public void testAdminEndpoint_WithoutAuth() {
given()
.when().get("/admin")
.then().statusCode(Response.Status.UNAUTHORIZED.getStatusCode());
}
}
@QuarkusTest
public class PublicResourceTest {
@Test
public void testPublicEndpoint() {
given()
.when().get("/public")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.body(is("hello user"));
}
}
Funktioniert!
Fazit
Die gestellte Aufgabe ist wirklich schnell und einfach gelöst. Leider ist die Dokumentation [2] dazu etwas wirr bzw. umständlich gehalten. Das mag sicherlich daran liegen, dass die geschilderte Anforderung doch eher die Ausnahme darstellt und man üblicherweise eine Lösung wie OAuth oder LDAP integrieren möchte.
Die beschriebene Konfiguration könnte nun auch problemlos um weitere Benutzer, Rollen, Permissions und Policies erweitert werden. Man sollte sich jedoch fragen, ob es bei einer Multi-User-Applikation nicht sinnvoller ist zumindest die Benutzerverwaltung in eine Datenbank zu schieben, wenn nicht sogar eher eine der bereits genannten Lösungen zu nutzen.
Eine lauffähige Demo ist auf Github zu finden: https://github.com/pklink/quarkus-simple-basic-auth
(Titelbild von Ivan Bandura)



