Spring Boot 2: /health Endpoint überschreiben

Im Grunde geht’s sogar um zwei Themen, die beide relativ unklar sind, wenn man sich die Einträge auf Stack Overflow ansieht:

  1. wie pretty-printe ich den (JSON-)Output eines Spring-Endpoints
  2. wie überschreibe ich Default-Endpoints (denn das kann eine Lösung für 1. sein)

Bspw. hier, hier, hier, hier. Es gibt dort einige Lösungsvorschläge, mehr oder weniger aufwändig, viele für Spring Boot 1.x – hier eine weitere (für Spring Boot 2), imho sehr elegante (mit viel Input von Nils!):

Dann wird /health auf /internal/health (oder irgendwas) umgeleitet, und danach /prettyhealth auf /health:

et voilà.

Spring Boot: @ConditionalOnMissingProperty

Spring Boot bietet ein Conditional namens ConditionalOnProperty, die die Component nur dann registriert, wenn eine gewisse System-Property entsprechend (siehe Logik unter dem Link) gesetzt ist – und ggf. auch, wenn das Property fehlt und matchIfMissing auf true gesetzt ist.

Das “auch” störte mich in einem Fall, weil ich zwar gerne gewisse Komponenten per System-Property einkonfigurieren können möchte. Aber wenn ich diese nicht habe, brauche ich Fallback-Komponenten, und dafür eine Annotation, die nur triggert, wenn das Property fehlt.

Ansage ist hier:

Since detecting missing properties can be problematic, and we’ve not got a direct need for this ourselves, I think your own custom condition is the best approach.

, was ich hiermit getan habe. Jeder Kommentar erwünscht (gerne auf gist, aber wer keinen Account hat, kann auch hier).

Pre-shared key Authentifizierung mit Spring Boot

Pre-Shared Keys sind mit Spring Boot eine Geschichte voller Missverständnisse. Hier jeweils meine:

HTTP/HTTPS

In der Theorie scheint die Verwendung von https “überflüssig”, denn wenn ich eh nur vorher als “vertrauenswürdig” zertifizierte Clients zulasse, warum dann noch die Verbindung absichern. Naheliegende Antwort: Austausch von Identitäten über http ist nie gut – ⚠

Also https in den application.properties (/application.yml) der Server-Anwendung einschalten:

Zertifikatserstellung

Die eigentliche Idee ist jetzt: Jeder Client muss ein “trusted” Zertifikat vorweisen. Diese Zertifikate werden in einem “Trust-Store” abgelegt, den der Server bekommt. Der Client bekommt sein Zertifikat ebenfalls, dort in einem “Key-Store”, um sich auszuweisen. Andersrum analog: Das Serverzertifikat bekommt der Client in einem Trust-Store, um den https-Server zu verifizieren, der Server legt das in seinem Key-Store ab.

Insgesamt sieht die Erstellung so aus:

Wichtig dabei: Die verwendeten Passwörter (klar), aber auch der verwendete Name, beides brauchen wir unten.

PS, siehe unten und hier

Zertifikate einbinden

Die Zertifikate kann man dem Server jetzt über einen TomcatConnector bekannt machen, muss man in Spring Boot aber nicht (⚠; Danke, Nils). Stattdessen genügt Folgendes, ebenfalls in den application.properties:

Hier verweise ich auf Key-Stores in der .jar, was seit Tomcat 7.0.66+ funktioniert. ⚠

In der Client-Anwendung sieht das etwas anders aus, zuerst die application.properties:

Verwendung dann beispielhaft in einem RestTemplate (org.apache.http.* gibt’s hier)

ACHTUNG: Der NoopHostnameVerifier war hier nötig, da ich das Zertifikat nicht mit expliziten Hostnames erstellt habe, in Production würde man das wohl tun. ⚠

Userverwaltung

Natürlich kann man mehrere Client-Zertifikate verwenden, und natürlich kann man jedem Client separate Rechte zuweisen. In der Server-Anwendung:

(Quelle und Stack Overflow)

btw: Spring Security bringt einen default User mit (“user”, Passwort wird auf der Konsole beim Hochfahren ausgegeben). Deaktivierung in den application.properties:

hth

Spring: JSON aus HttpServletRequest

Vorab: Ja, es gibt @RequestBody. Und es ist hässlich. Ich habe es nicht mal eingebaut. Aber ich musste es googlen und will das nicht noch mal tun müssen:

PS zu Zeile 2 und zu Zeile 4.

Spring Security: isFullyAuthenticated()

ist stellenweise verwirrend beschrieben. Hier heißt es

Returns true if the user is not an anonymous or a remember-me user

, wobei die Klammerung fehlt: “not” bezieht sich auf beides! Hier ist es IMHO auf andere Art verwirrend, aber zumindest nicht mehrdeutig:

Determines if the SecurityExpressionOperations.getAuthentication() authenticated without the use of remember me
HTH

Spring (Boot) erweitern

Angenommen, ich würde Web-Applikationen verkaufen. Zum Teil customized auf Basis desselben Cores, zum Teil unabhängig davon. Mein Code müsste also modular sein; ich möchte Libraries wieder­­verwenden, ganze Features ein- und ausschalten können, vielleicht Teile nur für einen Kunden überschreiben. Wie würde ich das wohl mit Spring Boot machen?

Sucht man im Internet nach Modularisierung von Spring (Boot) Anwendungen, landet man recht schnell bei Microservices:

While there is no precise definition of this architectural style, there are certain common characteristics […]: an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API.

Zuerst mal nützt mir das noch nichts für wiederzuverwendende Libraries. Aber selbst wenn, dann ist mir zugegebenermaßen nicht ganz klar, wie man sich das in einer Webapplikation vorzustellen hat: Kommuniziert jeder Teil der Anwendung (Chat, Kalender, …) nur mit “seinem” Backend, separiert zum Beispiel über Ports? Oder läuft alles über Port 80 an den Server, und der verteilt das intern an den jeweiligen Prozess? So oder so müsste man das anwendungsweit berücksichtigen (richtig?). Nicht falsch verstehen: Ich sehe die Vorteile (schnelles Austauschen eines Service, Ausfallsicherheit der Dach-Anwendung. Skalierbarkeit), <update>Hier ein gutes Tutorial zum Thema “microservices mit Spring, Spring Boot and Spring Cloud”</update> will mich hier aber auf einen anderen Ansatz konzentrieren:

Denn “schöner” (im Sinne von gewohnter) wäre es, wenn man die einzelnen Module zu einer Anwendung zusammenstecken könnte, trotzdem mit den o.g. Eigenschaften… Und letztendlich ist das genau, wie Spring Boot an sich funktioniert: Per Maven (jaja, oder Gradle) konfiguriert man, welche “Starter” man braucht, und plötzlich hat man zB JPA-Support, kann die Adapter aber frei austauschen oder umkonfigurieren. Interessant wäre es “nur” noch, wie die Starter an sich aussehen (wie gut oder schnell lassen sie sich (weiter-)entwickeln?) und ob sie zB auch eigene URL-Endpoints erlauben (nötig für Module mit Frontendanbindung wie Kalender oder Chat).

Vorab – das geht alles.

  • Jeder Starter ist für sich eine vollwertige eigenständige Webanwendung, kann aber trotzdem ohne Änderung als Dependency eingebunden werden. Das macht das Entwickeln extrem komfortabel.
  • Die Dependencies bringen eigene Endpoints mit, die aber ebenfalls dynamisch “woanders hingeroutet” werden können. Konfiguration etc. ist ebenfalls, auch partiell, überschreibbar.
  • Es gibt im einbindenden Projekt keine zwingende Abhängigkeit im Code zum Starter. Man kann aber, und das sogar komfortabel über konditionale Annotations, darauf reagieren, ob eine Dependency da ist oder nicht. Und zB default-Implementierungen aus der Dependency überschreiben.

Im Detail: Hier gibt es ein exzellentes Tutorial, das ich mir erlaubt habe zu übernehmen; hier gibt es meine entzerrte, “white-label”-Implementierung der interessantesten Punkte:

  1. Jeder Starter (“Service A”, “Service B”) ist ein vollständiges Spring Boot Projekt, und standalone lauffähig. (“Some Projekt” ist natürlich auch standalone, allerdings müssen “Service A” und “Service B” in das lokale Repo installiert sein, siehe README)
  2. Wenn ein Starter in ein anderes Projekt eingebunden wird, muss man Spring sagen, welche Klasse die Basis-Konfiguration enthält. Dazu legt man /src/main/resources/META-INF/spring.factories an und hinterlegt die Klasse wie im Beispiel-Code.
  3. Um Namenskonflikte der Beans zu vermeiden, sollte man @ComponentScan.nameGenerator verwenden – zur Not tut es aber auch das name-Feld von @Component wie im Beispiel.
  4. Konfigurationsdateien werden per @PropertySource(“classpath:acme-aservice.properties”) eingebunden. Werte daraus werden per @Value(“${aservice.messages.test1}”) private String test1; injected oder bsplw. per @RequestMapping(“${aservice.mapping.url:/service-a}”) in einem dadurch konfigurierbaren Routing verwendet (nach dem Doppelpunkt folgt der default).
  5. Konfigurationsdateien können von haus aus nur komplett überschrieben werden, indem sie unter demselben Dateinamen im Rahmenprojekt abgelegt werden. In der Beispielimplementierung habe ich eine Methode @PostConstruct public void initialize() in “Some Project”, die das aufbohrt, und das Überschreiben von einzelnen Settings erlaubt, auch wenn diese in einer anders benannten Datei definiert sind.
  6. Das folgende Konstrukt in com.acme.bservice.IndexController erlaubt das Ersetzen von Beispielimplementierungen (MessageProvider ist ein Interface):
        @Autowired(required = false)
         public void setMessageProvider(MessageProvider messageProvider)
    Ersetzt wird der default dann völlig transparent, indem man nur das Interface MessageProvider implementiert und die Implementierung als @Component annotiert.

Für Details der Implementierung siehe Code auf Github – der ist wirklich überschaubar: “Service A” hat zwei Klassen plus eine Config, “Service B” drei Klassen, “Some Project” zwei Klassen plus Config 🙂

Spring Data JPA: Subqueries

JPQL-Subqueries können nicht im FROM stehen – aber Native Queries haben diese Einschränkung nicht (zwingend). Klar, denn native Queries sind eben nativ, sprich in der Sprache der jeweiligen Datenbank. Wer also damit leben kann, die Portabilität zu verlieren, der kann wie folgt “beliebige” Queries verwenden:

Spring: Dependency Injection in statische Variablen

Es gibt n+1 Artikel, warum das architektonisch keinen Sinn ergeben würde – aber wenn man das trotzdem möchte, findet sich hier die Lösung (leicht abgewandelt, zB ist der Setter privat), die keine Konstruktor-Parameter erfordert:

PS: Der Setter wird nur beim Startup aufgerufen, und nicht mit jedem Bean, insofern entsteht dadurch kein Performance-Issue.

PPS, Achtung: Dieser Kommentar hat recht, deshalb Vorsicht! Ggf. sollte das Feld transient sein

ungetestete Alternative

via:

Spring Boot Security: 403 Forbidden

Es gibt durchaus gute Tutorials und Examples für Spring Boot Security. Nur benutzen die alle Thymeleaf als Templating Engine. Und Thymeleaf hat eine Besonderheit: Wenn man im Form “th:action” statt “action” verwendet, dann wird automatisch ein hidden Input Field mit dem CSRF-Token injected!

Wenn man stattdessen bsplw. Velocity verwendet, und das Template 1:1 übersetzt, merkt man nicht, dass etwas fehlt m( Der Server gibt “403 Forbidden” ohne weitere Fehlermeldung zurück (Tipp zum Debuggen: Der Request wird irgendwann auf die Error-Route umgebogen, aber der Fehler (hier noch inklusive Fehlermeldung) steht im Response-Object).

Offenbar muss man den Token dann manuell dem Template übergeben (Quelle, weicht aber leicht ab):

Gegenprobe:

Update

Übrigens: Hier wird beschrieben, wie man in Thymeleaf den Token in jeden Ajax-Request bekommt. Leider funktioniert das so nicht, denn um den Platzhalter zu ersetzen, muss man th:content verwenden (Quelle indirekt), sonst wird der Platzhalter nicht ausgewertet m(

Dann: Siehe hier für das Absetzen der Requests (in jQuery oder Angular)