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-policy
erstellen zu der alle Benutzer mit der Rolleadmin
gehören - Permission namens
admin-permission
erstellen und den Pfad/admin*
zuweisen (das*
dient hier als Wildcard. Der Pfad matched also sowohl auf/admin
als bspw. auch auf/admin/foo/bar
) - Zuvor erstellte
admin-permission
die Policyadmin-policy
hinzufügen - Embedded Benutzerverwaltung aktivieren
- Klartextpasswörter aktivieren (ansonsten werden die Passwörter in dem Format
HEX( MD5( username ":" realm ":" password ) )
erwartet - Benutzer
admin
mit dem Passwortpassword
erstellen - Benutzer
admin
die Rolleadmin
zuweisen
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: github.com/pklink/quarkus-simple-basic-auth
(Titelbild von Ivan Bandura)