Schnelle Einführung in die Spring Cloud-Konfiguration

1. Übersicht

Spring Cloud Config ist der Client / Server-Ansatz von Spring zum Speichern und Bereitstellen verteilter Konfigurationen in mehreren Anwendungen und Umgebungen.

Dieser Konfigurationsspeicher ist idealerweise unter Git- Versionskontrolle versioniert und kann zur Laufzeit der Anwendung geändert werden. Während es sehr gut in Spring-Anwendungen passt, die alle unterstützten Konfigurationsdateiformate zusammen mit Konstrukten wie Environment , PropertySource oder @Value verwenden , kann es in jeder Umgebung verwendet werden, in der jede Programmiersprache ausgeführt wird.

In diesem Artikel konzentrieren wir uns auf ein Beispiel für die Einrichtung eines Git- gestützten Konfigurationsservers, die Verwendung in einem einfachen REST- Anwendungsserver und die Einrichtung einer sicheren Umgebung mit verschlüsselten Eigenschaftswerten.

2. Projekteinrichtung und Abhängigkeiten

Um sich auf das Schreiben von Code vorzubereiten, erstellen wir zuerst zwei neue Maven- Projekte. Das Serverprojekt basiert auf dem Spring-Cloud-Config-Server- Modul sowie den Spring-Boot-Starter-Security- und Spring-Boot-Starter-Web- Starter-Paketen:

 org.springframework.cloud spring-cloud-config-server   org.springframework.boot spring-boot-starter-security   org.springframework.boot spring-boot-starter-web 

Für das Client-Projekt benötigen wir jedoch nur die Spring-Cloud-Starter-Konfiguration und die Spring-Boot-Starter-Web-Module :

 org.springframework.cloud spring-cloud-starter-config   org.springframework.boot spring-boot-starter-web 

3. Eine Config Server-Implementierung

Der Hauptteil der Anwendung ist eine Konfigurationsklasse - genauer gesagt eine @SpringBootApplication -, die alle erforderlichen Einstellungen über die automatisch konfigurierte Annotation @EnableConfigServer abruft:

@SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] arguments) { SpringApplication.run(ConfigServer.class, arguments); } } 

Jetzt müssen wir den Server- Port konfigurieren, auf dem unser Server empfangsbereit ist, und eine Git- URL, die unseren versionierten Konfigurationsinhalt bereitstellt. Letzteres kann mit Protokollen wie http , ssh oder einer einfachen Datei in einem lokalen Dateisystem verwendet werden.

Tipp: Wenn Sie mehrere Konfigurationsserverinstanzen verwenden möchten, die auf dasselbe Konfigurationsrepository verweisen, können Sie den Server so konfigurieren, dass Ihr Repo in einen lokalen temporären Ordner geklont wird. Beachten Sie jedoch, dass private Repositorys mit Zwei-Faktor-Authentifizierung schwer zu handhaben sind! In einem solchen Fall ist es einfacher, sie in Ihr lokales Dateisystem zu klonen und mit der Kopie zu arbeiten.

Es sind auch einige Platzhaltervariablen und Suchmuster zum Konfigurieren der Repository-URL verfügbar. Dies geht jedoch über den Rahmen unseres Artikels hinaus. Wenn Sie interessiert sind, ist die offizielle Dokumentation ein guter Anfang.

Wir müssen außerdem einen Benutzernamen und ein Kennwort für die Basisauthentifizierung in unserer application.properties festlegen , um ein automatisch generiertes Kennwort bei jedem Neustart der Anwendung zu vermeiden:

server.port=8888 spring.cloud.config.server.git.uri=ssh://localhost/config-repo spring.cloud.config.server.git.clone-on-start=true spring.security.user.name=root spring.security.user.password=s3cr3t

4. Ein Git-Repository als Konfigurationsspeicher

Um unseren Server zu vervollständigen, müssen wir ein Git- Repository unter der konfigurierten URL initialisieren , einige neue Eigenschaftendateien erstellen und sie mit einigen Werten popularisieren.

Der Name der Konfigurationsdatei setzt sich wie eine normale Spring application.properties zusammen , aber anstelle des Wortes 'application' wird ein konfigurierter Name verwendet, z. B. der Wert der Eigenschaft 'spring.application.name' des Clients, gefolgt von a Bindestrich und das aktive Profil. Zum Beispiel:

$> git init $> echo 'user.role=Developer' > config-client-development.properties $> echo 'user.role=User' > config-client-production.properties $> git add . $> git commit -m 'Initial config-client properties'

Fehlerbehebung: Wenn Sie den Weg laufen ssh -related Authentifizierungsprobleme, doppelte Überprüfung ~ / .ssh / known_hosts und ~ / .ssh / authorized_keys auf dem SSH - Server!

5. Abfragen der Konfiguration

Jetzt können wir unseren Server starten. Die von unserem Server bereitgestellte Git- gestützte Konfigurations-API kann über die folgenden Pfade abgefragt werden:

/{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties

In dem der Platzhalter {label} auf einen Git-Zweig verweist, {application} auf den Anwendungsnamen des Clients und {profile} auf das aktuell aktive Anwendungsprofil des Clients.

So können wir die Konfiguration für unser geplantes Config - Client läuft unter Entwicklungsprofil im Zweig abrufen Master über:

$> curl //root:[email protected]:8888/config-client/development/master

6. Die Client-Implementierung

Als nächstes kümmern wir uns um den Kunden. Dies wird eine sehr einfache Client-Anwendung sein, die aus einem REST- Controller mit einer GET- Methode besteht.

Die Konfiguration zum Abrufen unseres Servers muss in einer Ressourcendatei mit dem Namen bootstrap.application abgelegt werden , da diese Datei (wie der Name schon sagt) sehr früh geladen wird, während die Anwendung gestartet wird:

@SpringBootApplication @RestController public class ConfigClient { @Value("${user.role}") private String role; public static void main(String[] args) { SpringApplication.run(ConfigClient.class, args); } @GetMapping( value = "/whoami/{username}", produces = MediaType.TEXT_PLAIN_VALUE) public String whoami(@PathVariable("username") String username) { return String.format("Hello! You're %s and you'll become a(n) %s...\n", username, role); } }

Neben dem Anwendungsnamen haben wir auch das aktive Profil und die Verbindungsdetails in unsere bootstrap.properties eingefügt :

spring.application.name=config-client spring.profiles.active=development spring.cloud.config.uri=//localhost:8888 spring.cloud.config.username=root spring.cloud.config.password=s3cr3t

Zur Prüfung, ob die Konfiguration richtig von unserem Server und empfangen Rolle Wert wird injiziert in unserer Controller - Methode, locken wir sie einfach nach den Client booten:

$> curl //localhost:8080/whoami/Mr_Pink

Wenn die Antwort wie folgt lautet, funktionieren unser Spring Cloud Config Server und sein Client vorerst einwandfrei :

Hello! You're Mr_Pink and you'll become a(n) Developer...

7. Verschlüsselung und Entschlüsselung

Voraussetzung : Um kryptografisch starke Schlüssel zusammen mit Spring-Verschlüsselungs- und Entschlüsselungsfunktionen verwenden zu können, muss in Ihrer JVM die JCE-Richtliniendatei (Java Cryptography Extension) mit unbegrenzter Stärke installiert sein . Diese können beispielsweise von Oracle heruntergeladen werden. Befolgen Sie zur Installation die Anweisungen im Download. Einige Linux-Distributionen bieten über ihre Paketmanager auch ein installierbares Paket an.

Da der Konfigurationsserver die Ver- und Entschlüsselung von Eigenschaftswerten unterstützt, können Sie öffentliche Repositorys als Speicher für vertrauliche Daten wie Benutzernamen und Kennwörter verwenden. Verschlüsselten Werten wird die Zeichenfolge {cipher} vorangestellt und kann durch einen REST-Aufruf des Pfads '/ encrypt' generiert werden , wenn der Server für die Verwendung eines symmetrischen Schlüssels oder eines Schlüsselpaars konfiguriert ist.

Ein Endpunkt zum Entschlüsseln ist ebenfalls verfügbar. Beide Endpunkte akzeptieren einen Pfad mit Platzhaltern für den Namen der Anwendung und ihr aktuelles Profil: '/ * / {name} / {profile}' . Dies ist besonders nützlich für die Steuerung der Kryptografie pro Client. Bevor sie jedoch nützlich werden, müssen Sie einen kryptografischen Schlüssel konfigurieren, den wir im nächsten Abschnitt ausführen werden.

Tipp: Wenn Sie zum Aufrufen der En- / Entschlüsselungs-API Curl verwenden, ist es besser, die Option –data-urlencode (anstelle von –data / -d ) zu verwenden oder den Header 'Content-Type' explizit auf 'text / plain' zu setzen ' . Dies gewährleistet einen korrekten Umgang mit Sonderzeichen wie '+' in den verschlüsselten Werten.

Wenn ein Wert beim Abrufen über den Client nicht automatisch entschlüsselt werden kann, wird sein Schlüssel mit dem Namen selbst umbenannt, dem das Wort "ungültig" vorangestellt ist. Dies sollte beispielsweise die Verwendung eines verschlüsselten Wertes als Passwort verhindern.

Tipp: Wenn Sie ein Repository mit YAML-Dateien einrichten, müssen Sie Ihre verschlüsselten und vorangestellten Werte in einfache Anführungszeichen setzen! Bei Eigenschaften ist dies nicht der Fall.

7.1. CSRF

By default Spring Security enables CSRF protection for all the requests sent to our application.

Therefore, to be able to use the /encrypt and /decrypt endpoints, let's disable the CSRF for them:

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf() .ignoringAntMatchers("/encrypt/**") .ignoringAntMatchers("/decrypt/**"); super.configure(http); } }

7.2. Key Management

The config server is per default enabled to encrypt property values in a symmetric or asymmetric way.

To use symmetric cryptography, you simply have to set the property ‘encrypt.key' in your application.properties to a secret of your choice. Alternatively you can pass-in the environment variable ENCRYPT_KEY.

For asymmetric cryptography, you can set ‘encrypt.key' to a PEM-encoded string value or configure a keystore to use.

Because we need a highly secured environment for our demo server, we chose the latter option and generating a new keystore, including a RSA key-pair, with the Java keytool first:

$> keytool -genkeypair -alias config-server-key \ -keyalg RSA -keysize 4096 -sigalg SHA512withRSA \ -dname 'CN=Config Server,OU=Spring Cloud,O=Baeldung' \ -keypass my-k34-s3cr3t -keystore config-server.jks \ -storepass my-s70r3-s3cr3t

After that, we're adding the created keystore to our server's bootstrap.properties and re-run it:

encrypt.keyStore.location=classpath:/config-server.jks encrypt.keyStore.password=my-s70r3-s3cr3t encrypt.keyStore.alias=config-server-key encrypt.keyStore.secret=my-k34-s3cr3t

As next step we can query the encryption-endpoint and add the response as value to a configuration in our repository:

$> export PASSWORD=$(curl -X POST --data-urlencode d3v3L \ //root:[email protected]:8888/encrypt) $> echo "user.password={cipher}$PASSWORD" >> config-client-development.properties $> git commit -am 'Added encrypted password' $> curl -X POST //root:[email protected]:8888/refresh

To test, if our setup works correctly, we're modifying the ConfigClient class and restart our client:

@SpringBootApplication @RestController public class ConfigClient { ... @Value("${user.password}") private String password; ... public String whoami(@PathVariable("username") String username) { return String.format("Hello! You're %s and you'll become a(n) %s, " + "but only if your password is '%s'!\n", username, role, password); } }

A final query against our client will show us, if our configuration value is being correct decrypted:

$> curl //localhost:8080/whoami/Mr_Pink Hello! You're Mr_Pink and you'll become a(n) Developer, \ but only if your password is 'd3v3L'!

7.3. Using Multiple Keys

If you want to use multiple keys for encryption and decryption, for example: a dedicated one for each served application, you can add another prefix in the form of {name:value} between the {cipher} prefix and the BASE64-encoded property value.

The config server understands prefixes like {secret:my-crypto-secret} or {key:my-key-alias} nearly out-of-the-box. The latter option needs a configured keystore in your application.properties. This keystore is searched for a matching key alias. For example:

user.password={cipher}{secret:my-499-s3cr3t}AgAMirj1DkQC0WjRv... user.password={cipher}{key:config-client-key}AgAMirj1DkQC0WjRv...

For scenarios without keystore you have to implement a @Bean of type TextEncryptorLocator which handles the lookup and returns a TextEncryptor-Object for each key.

7.4. Serving Encrypted Properties

If you want to disable server-side cryptography and handle decryption of property-values locally, you can put the following in your server's application.properties:

spring.cloud.config.server.encrypt.enabled=false

Furthermore you can delete all the other ‘encrypt.*' properties to disable the REST endpoints.

8. Conclusion

Now we are able to create a configuration server to provide a set of configuration files from a Git repository to client applications. There are a few other things you can do with such a server.

For example:

  • Serve configuration in YAML or Properties format instead of JSON – also with placeholders resolved. Which can be useful, when using it in non-Spring environments, where the configuration is not directly mapped to a PropertySource.
  • Serve plain text configuration files – in turn optionally with resolved placeholders. This can be useful for example to provide an environment-dependent logging-configuration.
  • Embed the config server into an application, where it configures itself from a Git repository, instead of running as standalone application serving clients. Therefore some bootstrap properties must be set and/or the @EnableConfigServer annotation must be removed, which depends on the use case.
  • Stellen Sie den Konfigurationsserver bei Spring Netflix Eureka Service Discovery zur Verfügung und aktivieren Sie die automatische Servererkennung in Konfigurationsclients. Dies ist wichtig, wenn der Server keinen festen Standort hat oder sich an seinem Standort bewegt.

Zum Abschluss finden Sie den Quellcode zu diesem Artikel auf Github .