Перейти к основному содержимому

Краткое введение в конфигурацию Spring Cloud

· 9 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Обзор

Spring Cloud Config — это клиент-серверный подход Spring для хранения и обслуживания распределенных конфигураций в нескольких приложениях и средах.

Это хранилище конфигурации идеально управляется системой управления версиями Git и может быть изменено во время выполнения приложения. Хотя он очень хорошо подходит для приложений Spring, использующих все поддерживаемые форматы файлов конфигурации вместе с такими конструкциями, как Environment , PropertySource или @Value , его можно использовать в любой среде, где работает любой язык программирования.

В этом руководстве мы сосредоточимся на том, как настроить сервер конфигурации с поддержкой Git , использовать его в простом сервере приложений REST и настроить безопасную среду, включая зашифрованные значения свойств.

2. Настройка проекта и зависимости

Во-первых, мы создадим два новых проекта Maven . Серверный проект опирается на модуль spring-cloud-config-server , а также на стартовые пакеты spring-boot-starter-security и spring-boot-starter-web :

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Однако для клиентского проекта нам нужны только модули spring-cloud-starter-config и spring-boot-starter-web :

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

3. Реализация сервера конфигурации

Основная часть приложения — это класс конфигурации, а точнее @SpringBootApplication , который извлекает все необходимые настройки с помощью аннотации автонастройки @EnableConfigServer:

@SpringBootApplication
@EnableConfigServer
public class ConfigServer {

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

Теперь нам нужно настроить порт сервера, на котором прослушивается наш сервер, и Git -url, который предоставляет содержимое конфигурации с контролируемой версией. Последний можно использовать с такими протоколами, как http , ssh или простой файл в локальной файловой системе.

Совет: если мы планируем использовать несколько экземпляров сервера конфигурации, указывающих на один и тот же репозиторий конфигурации, мы можем настроить сервер для клонирования нашего репозитория в локальную временную папку. Но помните о частных репозиториях с двухфакторной аутентификацией; с ними трудно справиться! В таком случае проще клонировать их в нашу локальную файловую систему и работать с копией.

Также доступны некоторые переменные-заполнители и шаблоны поиска для настройки URL-адреса репозитория ; однако это выходит за рамки нашей статьи. Если вы хотите узнать больше, официальная документация — хорошее место для начала.

Нам также нужно установить имя пользователя и пароль для базовой аутентификации в нашем application.properties , чтобы избежать автоматического создания пароля при каждом перезапуске приложения:

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. Репозиторий Git как хранилище конфигурации

Чтобы завершить наш сервер, мы должны инициализировать репозиторий Git под настроенным URL-адресом, создать несколько новых файлов свойств и заполнить их некоторыми значениями.

Имя файла конфигурации составляется как обычное Spring application.properties , но вместо слова «приложение» используется настроенное имя, такое как значение свойства «spring.application.name» клиента, затем тире и активный профиль. Например:

$> 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'

Устранение неполадок: если мы столкнемся с проблемами аутентификации, связанными с ssh, мы можем дважды проверить ~ /.ssh/known_hosts и ~/.ssh/authorized_keys на нашем ssh-сервере.

5. Запрос конфигурации

Теперь мы можем запустить наш сервер. API конфигурации с поддержкой Git , предоставляемый нашим сервером, можно запросить, используя следующие пути:

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

Заполнитель {label} относится к ветке Git, {application} — к имени клиентского приложения, а {profile} — к текущему профилю активного приложения клиента.

Таким образом, мы можем получить конфигурацию для нашего запланированного клиента конфигурации, работающего под профилем разработки в мастере ветки, через:

$> curl http://root:s3cr3t@localhost:8888/config-client/development/master

6. Реализация клиента

Далее займемся клиентом. Это будет очень простое клиентское приложение, состоящее из контроллера REST с одним методом GET .

Чтобы получить наш сервер, конфигурация должна быть помещена в файл application.properties . Spring Boot 2.4 представил новый способ загрузки данных конфигурации с использованием свойства spring.config.import , который теперь является способом привязки к Config Server по умолчанию:

@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);
}
}

В дополнение к имени приложения мы также помещаем активный профиль и сведения о подключении в наш application.properties :

spring.application.name=config-client
spring.profiles.active=development
spring.config.import=optional:configserver:http://root:s3cr3t@localhost:8888

Это позволит подключиться к серверу конфигурации по адресу http://localhost:8888, а также будет использовать базовую безопасность HTTP при инициировании подключения. Мы также можем установить имя пользователя и пароль отдельно, используя

spring.cloud.config.username

и

spring.cloud.config.password

свойства соответственно.

В некоторых случаях мы можем захотеть потерпеть неудачу при запуске службы, если она не может подключиться к серверу конфигурации. Если это желаемое поведение, мы можем удалить необязательный: префикс, чтобы клиент остановился с исключением.

Чтобы проверить, правильно ли получена конфигурация с нашего сервера и введено ли значение роли в наш метод контроллера, мы просто скручиваем его после загрузки клиента:

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

Если ответ выглядит следующим образом, наш Spring Cloud Config Server и его клиент пока работают нормально:

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

7. Шифрование и дешифрование

Требование : чтобы использовать криптографически стойкие ключи вместе с функциями шифрования и дешифрования Spring, нам необходимо установить «Файлы политик юрисдикции неограниченной силы Java Cryptography Extension (JCE)», установленные на нашей JVM. Их можно скачать, например, с Oracle . Для установки следуйте инструкциям, включенным в загрузку. Некоторые дистрибутивы Linux также предоставляют устанавливаемый пакет через свои менеджеры пакетов.

Поскольку сервер конфигурации поддерживает шифрование и расшифровку значений свойств, мы можем использовать общедоступные репозитории в качестве хранилища конфиденциальных данных, таких как имена пользователей и пароли. Зашифрованные значения имеют префикс строки {cipher} и могут быть сгенерированы REST-вызовом пути «/encrypt», если сервер настроен на использование симметричного ключа или пары ключей.

Также доступна конечная точка для расшифровки. Обе конечные точки принимают путь, содержащий заполнители для имени приложения и его текущего профиля: '/*/{имя}/{профиль}'. Это особенно полезно для управления криптографией для каждого клиента. Однако, прежде чем они станут полезными, нам нужно настроить криптографический ключ, что мы и сделаем в следующем разделе.

Совет: если мы используем curl для вызова API шифрования/дешифрования, лучше использовать опцию –data-urlencode (вместо –data/-d ) или явно указать для заголовка Content-Type значение text/plain. ' . Это обеспечивает правильную обработку специальных символов, таких как «+», в зашифрованных значениях.

Если значение не может быть автоматически расшифровано во время выборки через клиент, его ключ переименовывается с самим именем со словом «недействительный» в начале. Это должно предотвратить использование зашифрованного значения в качестве пароля.

Совет: при настройке репозитория, содержащего файлы YAML, мы должны заключать наши зашифрованные и префиксные значения в одинарные кавычки. Однако это не относится к свойствам.

7.1. CSRF

По умолчанию Spring Security включает защиту CSRF для всех запросов, отправляемых нашему приложению.

Поэтому, чтобы иметь возможность использовать конечные точки /encrypt и /decrypt , давайте отключим для них CSRF:

@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. Ключевой менеджмент

По умолчанию сервер конфигурации может шифровать значения свойств симметричным или асимметричным способом.

Чтобы использовать симметричную криптографию , нам просто нужно установить для свойства 'encrypt.key' в нашем application.properties секрет по нашему выбору . В качестве альтернативы мы можем передать переменную среды ENCRYPT_KEY .

Для асимметричной криптографии мы можем установить «encrypt.key» в строковое значение в формате PEM или настроить используемое хранилище ключей .

Поскольку для нашего демонстрационного сервера нам нужна высокозащищенная среда, мы выберем последний вариант, наряду с созданием нового хранилища ключей, включая пару ключей RSA , с помощью Java keytool :

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

Затем мы добавим созданное хранилище ключей в приложение .properties нашего сервера и перезапустим его:

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

Затем мы запросим конечную точку шифрования и добавим ответ в качестве значения в конфигурацию в нашем репозитории:

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

Чтобы проверить, правильно ли работает наша установка, мы изменим класс ConfigClient и перезапустим наш клиент:

@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);
}
}

Наконец, запрос к нашему клиенту покажет нам, правильно ли расшифровывается значение нашей конфигурации:

$> curl http://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. Использование нескольких ключей

Если мы хотим использовать несколько ключей для шифрования и дешифрования, например выделенный для каждого обслуживаемого приложения, мы можем добавить еще один префикс в виде {имя:значение} между префиксом {шифр} и значением свойства в кодировке BASE64 .

Конфигурационный сервер понимает префиксы типа {secret:my-crypto-secret} или {key:my-key-alias} практически "из коробки". Для последнего варианта требуется настроенное хранилище ключей в нашем application.properties . В этом хранилище ключей выполняется поиск соответствующего псевдонима ключа. Например:

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

Для сценариев без хранилища ключей мы должны реализовать @Bean типа TextEncryptorLocator, который обрабатывает поиск и возвращает объект TextEncryptor для каждого ключа.

7.4. Обслуживание зашифрованных свойств

Если мы хотим отключить криптографию на стороне сервера и обрабатывать расшифровку значений свойств локально, мы можем поместить следующее в application.properties нашего сервера :

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

Кроме того, мы можем удалить все остальные свойства «encrypt.*», чтобы отключить конечные точки REST .

8. Заключение

Теперь мы можем создать сервер конфигурации для предоставления набора файлов конфигурации из репозитория Git клиентским приложениям. Есть также несколько других вещей, которые мы можем сделать с таким сервером.

Например:

  • Подавать конфигурацию в формате YAML или Properties вместо JSON, также с разрешенными заполнителями. Это может быть полезно при использовании его в средах, отличных от Spring, где конфигурация не сопоставляется напрямую с PropertySource .
  • По очереди обслуживайте файлы конфигурации в виде обычного текста, возможно, с разрешенными заполнителями. Например, это может быть полезно для обеспечения конфигурации ведения журнала, зависящей от среды.
  • Встройте сервер конфигурации в приложение, где он настраивается из репозитория Git вместо того, чтобы работать как отдельное приложение, обслуживающее клиентов. Поэтому мы должны установить некоторые свойства и/или мы должны удалить аннотацию @EnableConfigServer , что зависит от варианта использования.
  • Сделайте сервер конфигурации доступным при обнаружении службы Spring Netflix Eureka и включите автоматическое обнаружение сервера в клиентах конфигурации. Это становится важным, если сервер не имеет фиксированного местоположения или перемещается в своем местоположении.

Как всегда, исходный код этой статьи доступен на Github .