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
.