Рубрики
Без рубрики

Безопасные микросервисы Spring от службы к службе с поддержкой HTTPS и OAuth 2.0

Вы создали архитектуру микросервисов, но обеспечили ли вы связь между службами? Этот пост покажет вам, как это сделать. С тегами java, микросервисы, spring boot, учебное пособие.

Построение архитектуры микросервисов возможно с минимальным количеством кода, если вы используете Spring Boot, Spring Cloud и Spring Cloud Config. Упакуйте все в контейнеры Docker, и вы сможете запускать все с помощью Docker Compose. Если вы обмениваетесь данными между службами, вы можете обеспечить некоторую безопасность своих служб, не раскрывая их порты в вашем файле docker-compose.yml .

Но что произойдет, если кто-то случайно откроет порты ваших микросервисных приложений? Будут ли они по-прежнему в безопасности или кто-нибудь сможет получить доступ к их данным?

В этом посте я покажу вам, как использовать HTTPS и OAuth 2.0 для обеспечения безопасной связи между службами.

Разработайте стек микросервисов с помощью Spring Boot, Spring Cloud и Spring Cloud Config

Я собираюсь сократить процесс создания полного стека микросервисов с помощью Spring Boot, Spring Cloud и Spring Cloud Config. Мой приятель Рафаэль написал пост о том, как создавать микросервисы Spring и настраивать их для производства . Вы можете использовать его пример приложения в качестве отправной точки. Клонируйте okta-spring-microservices-docker-example проект:

git clone https://github.com/oktadeveloper/okta-spring-microservices-docker-example.git spring-microservices-security
cd spring-microservices-security

Для этого проекта требуются два приложения OpenID Connect на Okta, одно для разработки и одно для производства. Вам нужно будет создать каждое приложение на Okta, если вы не проходили вышеупомянутый учебник.

Создавайте приложения OpenID Connect на Okta

Вы можете зарегистрироваться для бесплатной учетной записи разработчика , которая позволит вам иметь до 1000 активных пользователей в месяц за 0 долларов. Этого должно быть достаточно для данного примера.

Почему Окта? Потому что аутентификацию писать неинтересно. В Okta есть API аутентификации и управления пользователями, которые позволяют вам быстрее разрабатывать свои приложения. Наш API и SDK облегчают вам аутентификацию, управление и защиту ваших пользователей за считанные минуты.

После создания учетной записи создайте новое веб-приложение на панели управления Okta ( Приложения > Добавить приложение ). Дайте приложению имя, которое вы запомните, продублируйте существующий URI перенаправления входа в систему и заставьте его использовать HTTPS. Нажмите Сделано .

Результат должен выглядеть так, как показано на скриншоте ниже.

Создайте другое приложение для производства. Я вызвал свои Микросервисы Prod .

В проекте, который вы клонировали, измените config/school-ui.properties , чтобы получить настройки из вашего приложения разработчика.

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={devClientId}
okta.oauth2.clientSecret={devClientId}

Эти настройки будут использоваться при индивидуальном запуске ваших приложений с помощью Maven. Производственные настройки используются при запуске с помощью Docker Compose. Измените config-data/school-ui-production.properties , чтобы получить настройки из вашего производственного приложения.

okta.oauth2.clientId={prodClientId}
okta.oauth2.clientSecret={prodClientId}

Вы можете видеть, что spring.profiles.active включает рабочий профиль в docker-compose.yml :

school-ui:
  image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOT
  environment:
    - JAVA_OPTS=
      -DEUREKA_SERVER=http://discovery:8761/eureka
      -Dspring.profiles.active=production
  restart: on-failure
  depends_on:
    - discovery
    - config
  ports:
    - 8080:8080

Docker Compose запускается из каталога, расположенного над приложениями, и считывает свои данные из каталога config-data . По этой причине вам нужно будет скопировать эти файлы свойств в этот каталог. Выполните следующие команды из корня этого проекта.

cp config/*.properties config-data/.

Запустите свой стек микросервисов Spring с помощью Docker Compose

В этом проекте есть агрегатор pom.xml в его корневом каталоге, что позволит вам создавать все проекты с помощью одной команды. Выполните следующие команды Maven для создания, тестирования и создания образов Docker для каждого проекта.

mvn clean install

СОВЕТ : Если у вас не установлен Maven, вы можете установить его с помощью SDKMAN! sdk установить maven

Когда процесс завершится, запустите все приложения { config, discovery, school-service и school-ui } с помощью Docker Compose. Смотрите Install Docker Compose если он у вас не установлен.

docker-compose up -d

СОВЕТ : Вы можете использовать Kitematic для просмотра журналов каждого приложения по мере его запуска.

Перейдите к http://localhost:8080 в вашем любимом браузере. После этого вы должны иметь возможность войти в систему и просмотреть список школьных классов.

Безопасность Spring и OAuth 2.0

В этом примере используется стартер весенней загрузки Okta , который представляет собой тонкий слой поверх Spring Security. Okta starter упрощает настройку и выполняет проверку аудитории в токене доступа. Это также позволяет вам указать утверждение, которое будет использоваться для создания Spring Security authorities.

Файл docker-compose.yml не предоставляет доступ к school-service внешнему миру. Он делает это, не указывая ports .

В проекте school-ui есть класс School Controller , который взаимодействует с school-service с помощью Spring/| RestTemplate .

@GetMapping("/classes")
@PreAuthorize("hasAuthority('SCOPE_profile')")
public ResponseEntity> listClasses() {

    return restTemplate
            .exchange("http://school-service/class", HttpMethod.GET, null,
                    new ParameterizedTypeReference>() {});
}

Вы заметите, что на конечной точке этого класса есть защита, но между службами безопасности не существует. Я покажу вам, как решить эту проблему в следующих шагах.

Во-первых, откройте порт school-service , чтобы имитировать, как кто-то жирно перебирает конфигурацию. Измените конфигурацию school-service в docker-compose.yml , чтобы открыть его порт.

school-service:
  image: developer.okta.com/microservice-docker-school-service:0.0.1-SNAPSHOT
  environment:
    - JAVA_OPTS=
      -DEUREKA_SERVER=http://discovery:8761/eureka
  depends_on:
    - discovery
    - config
  ports:
    - 8081:8081

Перезапустите все с помощью Docker Compose:

docker-compose down
docker-compose up -d

Вы увидите, что вам не нужно проходить аутентификацию, чтобы просмотреть данные по адресу http://localhost:8081 . Черт возьми! 😱

Убедитесь, что закрыл все ваши контейнеры Docker, прежде чем перейти к следующему разделу.

docker-compose down

HTTPS Везде!

HTTPS расшифровывается как “Безопасный” HTTP. HTTPS-соединения зашифрованы, и их содержимое значительно сложнее прочитать, чем HTTP-соединения. В последние годы наблюдается большое движение за повсеместное использование HTTPS, даже при разработке. Есть проблемы, с которыми вы можете столкнуться при работе с HTTPS, и хорошо бы выявить их на ранней стадии.

Let’s Encrypt – это центр сертификации, который предлагает бесплатные сертификаты HTTPS. У него также есть API-интерфейсы для автоматизации их обновления. Короче говоря, это делает HTTPS настолько простым, что нет причин не использовать его! См. Добавьте социальный логин в свое приложение JHipster/| для получения инструкций по использованию certbot с помощью Let's Encrypt для создания сертификатов.

Я также призываю вас проверить Пружинный загрузочный стартер ACME . Это загрузочный модуль Spring, который упрощает генерацию сертификатов с использованием Let’s Encrypt и протокола Automatic Certificate Management Environment (ACME).

Упростите локальный TLS с помощью mkcert

Недавно я нашел инструмент под названием mkcert , который позволяет создавать сертификаты localhost . Вы можете установить его с помощью Homebrew на macOS:

brew install mkcert
brew install nss # Needed for Firefox

Если вы используете Linux, вам нужно будет установить certutil первый:

sudo apt install libnss3-tools

Затем запустите команду brew install mkcert , используя Linuxbrew . Пользователи Windows могут использовать шоколад или совок .

Выполните следующие команды mkcert для создания сертификата для localhost , 127.0.0.1 , имя вашего компьютера и хост discovery (как указано в docker-compose.yml ).

mkcert -install
mkcert localhost 127.0.0.1 ::1 `hostname` discovery

Если при этом генерируются файлы с номером, переименуйте файлы так, чтобы у них не было номера.

mv localhost+2.pem localhost.pem
mv localhost+2-key.pem localhost-key.pem

HTTPS с пружинной загрузкой

Spring Boot не поддерживает сертификаты с расширением PEM , но вы можете преобразовать его в расширение PKCS12 , которое поддерживает Spring Boot. Вы можете использовать OpenSSL для преобразования сертификата и закрытого ключа в PKCS12. Это также будет необходимо для шифрования сгенерированных сертификатов Let’s Encrypt.

Запустите openssl для преобразования сертификата:

openssl pkcs12 -export -in localhost.pem -inkey \
localhost-key.pem -out keystore.p12 -name bootifulsecurity

Укажите пароль при появлении соответствующего запроса.

Создать файл https.env в корне вашего проекта и укажите следующие свойства, чтобы включить HTTPS.

export SERVER_SSL_ENABLED=true
export SERVER_SSL_KEY_STORE=../keystore.p12
export SERVER_SSL_KEY_STORE_PASSWORD={yourPassword}
export SERVER_SSL_KEY_ALIAS=bootifulsecurity
export SERVER_SSL_KEY_STORE_TYPE=PKCS12

Обновите файл .gitignore , чтобы исключить .env файлы таким образом, пароль хранилища ключей не попадает в систему управления версиями.

*.env

Запустите source https.en , чтобы установить эти переменные среды. Или, что еще лучше, добавьте этот лайк в свой .bashrc или .zshrc файл, чтобы эти переменные устанавливались для каждой новой оболочки. Да, вы также можете включить их в application.properties каждого приложения, но тогда вы храните секреты в системе управления версиями. Если вы не проверяете этот пример в системе управления версиями, вот настройки, которые вы можете скопировать/вставить.

server.ssl.enabled=true
server.ssl.key-store=../keystore.p12
server.ssl.key-store-password: {yourPassword}
server.ssl.key-store-type: PKCS12
server.ssl.key-alias: bootifulsecurity

Запустите приложение discovery :

cd discovery
source ../https.env
mvn spring-boot:run

Затем подтвердите, что вы можете получить к нему доступ по адресу https://localhost:8761 .

Открыть docker-compose.yml и измените все экземпляры http на https . Редактировать school-ui/src/main/java/.../ui/controller/SchoolController.java чтобы изменить вызов на school-service для использования HTTPS.

return restTemplate
        .exchange("https://school-service/class", HttpMethod.GET, null,
                new ParameterizedTypeReference>() {});

Обновите {config,school-service,school-ui}/src/main/resources/application.properties , чтобы добавить свойства, которые заставляют каждый экземпляр регистрироваться как защищенное приложение .

eureka.instance.secure-port-enabled=true
eureka.instance.secure-port=${server.port}
eureka.instance.status-page-url=https://${eureka.hostname}:${server.port}/actuator/info
eureka.instance.health-check-url=https://${eureka.hostname}:${server.port}/actuator/health
eureka.instance.home-page-url=https://${eureka.hostname}${server.port}/

Кроме того, измените адрес Eureka в каждом application.properties (и в bootstrap.yml ) на https://localhost:8761/eureka .

ПРИМЕЧАНИЕ : В application.properties в проекте school-ui не указан порт. Вам нужно будет добавить server.port=8080 .

На этом этапе вы должны иметь возможность запускать все свои приложения, выполнив следующее в каждом проекте (в отдельных окнах терминала).

source ../https.env
./mvnw spring-boot:start

Подтвердите, что все это работает по адресу https://localhost:8080 . Затем убейте все с помощью killall java .

Использование HTTPS с помощью Docker Compose

Docker не считывает данные из переменных среды, он не знает о вашем локальном центре сертификации (Центре сертификации), и вы не можете добавлять файлы из родительского каталога в изображение.

Чтобы исправить это, вам нужно скопировать хранилище ключей.p12 и localhost.pem в каталог каждого проекта. Первый будет использоваться для загрузки Spring, а второй будет добавлен в хранилище ключей Java на каждом изображении.

cp localhost.pem keystore.p12 config/.
cp localhost.pem keystore.p12 discovery/.
cp localhost.pem keystore.p12 school-service/.
cp localhost.pem keystore.p12 school-ui/.

Затем измените Dockerfile каждого проекта, чтобы скопировать сертификат и добавить его в хранилище доверия.

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/*.jar app.jar
ADD keystore.p12 keystore.p12
USER root
COPY localhost.pem $JAVA_HOME/jre/lib/security
RUN \
    cd $JAVA_HOME/jre/lib/security \
    && keytool -keystore cacerts -storepass changeit -noprompt \
    -trustcacerts -importcert -alias bootifulsecurity -file localhost.pem
ENV JAVA_OPTS=""
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar"]

Затем создайте файл .env с переменными среды для Spring Boot и HTTPS.

SERVER_SSL_ENABLED=true
SERVER_SSL_KEY_STORE=keystore.p12
SERVER_SSL_KEY_STORE_PASSWORD={yourPassword}
SERVER_SSL_KEY_ALIAS=bootifulsecurity
SERVER_SSL_KEY_STORE_TYPE=PKCS12
EUREKA_INSTANCE_HOSTNAME={yourHostname}

Вы можете получить значение для {yourHostname} , выполнив команду hostname .

Docker Compose имеет параметр конфигурации “env_file”, который позволяет вам считывать этот файл для переменных среды. Обновление docker-compose.yml для указания env_file для каждого приложения.

version: '3'
services:
  discovery:
    env_file:
      - .env
    ...
  config:
    env_file:
      - .env
    ...
  school-service:
    env_file:
      - .env
    ...
  school-ui:
    env_file:
      - .env
    ...

Вы можете убедиться, что он работает, запустив docker-compose config из вашего корневого каталога.

Запустите mvn clean install , чтобы перестроить все ваши образы Docker с включенным HTTPS для регистрации в Eureka. Тогда начинайте все сначала.

docker-compose up -d

Теперь все ваши приложения работают в Docker по протоколу HTTPS! Докажите это в https://localhost:8080 .

ПРИМЕЧАНИЕ : Если ваши приложения не запускаются или не могут взаимодействовать друг с другом, убедитесь, что ваше имя хоста соответствует тому, что у вас есть в .env .

Вы можете сделать еще одно улучшение безопасности: используйте OAuth 2.0 для защиты вашего школьного API-интерфейса.

Безопасность API с помощью OAuth 2.0

Добавьте Okta Spring Boot Starter и конфигурацию Spring Cloud в school-service/pom.xml :


    com.okta.spring
    okta-spring-boot-starter
    1.1.0


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

Затем создайте SecurityConfiguration.java класс в school-service/src/main/java/.../service/configuration :

package com.okta.developer.docker_microservices.service.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests().anyRequest().authenticated()
            .and()
            .oauth2ResourceServer().jwt();
    }
}

Создайте файл school-service/src/test/resources/test.properties и добавьте свойства, чтобы Oktas config прошел, и он не использовал обнаружение или сервер конфигурации при тестировании.

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId=TEST
spring.cloud.discovery.enabled=false
spring.cloud.config.discovery.enabled=false
spring.cloud.config.enabled=false

Затем измените ServiceApplicationTests.java чтобы загрузить этот файл для проверки свойств:

import org.springframework.test.context.TestPropertySource;

...
@TestPropertySource(locations="classpath:test.properties")
public class ServiceApplicationTests {
    ...
}

Добавьте файл school-service/src/main/resources/bootstrap.yml , который позволяет этому экземпляру считывать свою конфигурацию из конфигурации Spring Cloud.

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:https://localhost:8761/eureka}
spring:
  application:
    name: school-service
  cloud:
    config:
      discovery:
        enabled: true
        serviceId: CONFIGSERVER
      failFast: true

Затем скопируйте config/school-ui.properties , чтобы получить эквивалент school-service .

cp config/school-ui.properties config/school-service.properties

Для Docker Compose вам также потребуется создать config-data/school-service.properties со следующими настройками:

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={prodClientId}
okta.oauth2.clientSecret={prodClientId}

Вам также нужно будет изменить docker-compose.yml таким образом, school-service перезапускается при сбое.

school-service:
  ...
  restart: on-failure

СОВЕТ : Вы могли бы создать сервисное приложение на Okta, которое использует учетные данные клиента, но этот пост уже достаточно сложен. Дополнительную информацию об этом подходе см. в разделе Безопасная связь между серверами с помощью Spring Boot и OAuth 2.0 .

Последний шаг, который вам нужно будет сделать, это изменить Школьный контролер (в проекте school-ui ), чтобы добавить токен доступа OAuth 2.0 к запросу, который он отправляет на |/school-server .

Добавьте маркер доступа к RestTemplate

package com.okta.developer.docker_microservices.ui.controller;

import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;

import java.io.IOException;
import java.util.List;

@Controller
@RequestMapping("/")
public class SchoolController {

    private final OAuth2AuthorizedClientService authorizedClientService;
    private final RestTemplate restTemplate;

    public SchoolController(OAuth2AuthorizedClientService clientService,
                            RestTemplate restTemplate) { (1)
        this.authorizedClientService = clientService;
        this.restTemplate = restTemplate;
    }

    @RequestMapping("")
    public ModelAndView index() {
        return new ModelAndView("index");
    }

    @GetMapping("/classes")
    @PreAuthorize("hasAuthority('SCOPE_profile')")
    public ResponseEntity> listClasses(
            @AuthenticationPrincipal OAuth2AuthenticationToken authentication) { (2)

        OAuth2AuthorizedClient authorizedClient =
                this.authorizedClientService.loadAuthorizedClient(
                        authentication.getAuthorizedClientRegistrationId(),
                        authentication.getName()); (3)

        OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); (4)
        restTemplate.getInterceptors().add(getBearerTokenInterceptor(accessToken.getTokenValue())); (5)

        return restTemplate
                .exchange("https://school-service/class", HttpMethod.GET, null,
                        new ParameterizedTypeReference>() {});
    }

    private ClientHttpRequestInterceptor getBearerTokenInterceptor(String accessToken) {
        return (request, bytes, execution) -> {
            request.getHeaders().add("Authorization", "Bearer " + accessToken);
            return execution.execute(request, bytes);
        };
    }
}

1 Добавить Авторизованная клиентская служба OAuth2 зависимость от конструктора 2 Ввести Токен аутентификации OAuth2 в list Classes() метод 3 Создать OAuth2AuthorizedClient из аутентификации 4 Получите токен доступа от авторизованного клиента 5 Добавьте маркер доступа в заголовок Authorization

Вот и все! Поскольку school-ui и school-service используют одни и те же настройки приложения OIDC, сервер распознает и проверяет токен доступа (который также является JWT) и разрешает доступ.

На этом этапе вы можете выбрать запуск всех ваших приложений по отдельности с помощью ./mvn spring-boot:run или с помощью Docker Compose. Последний метод требует всего нескольких команд.

mvn clean install
docker-compose down
docker-compose up -d

Используйте HTTP Basic Auth для безопасной связи микросервиса с Eureka и Spring Cloud Config

Чтобы еще больше повысить безопасность между вашими микросервисами, сервером Eureka и конфигурацией Spring Cloud, вы можете добавить базовую аутентификацию HTTP. Чтобы сделать это, вам нужно будет добавить spring-boot-starter-security в качестве зависимости как в config и открытие проектов. Затем вам нужно будет указать spring.security.user.password для каждого из них и зашифровать его. Вы можете узнать больше о том, как это сделать, в Spring Cloud Config security docs .

Как только вы настроите Spring Security в обоих проектах, вы можете настроить URL-адреса так, чтобы в них были указаны имя пользователя и пароль. Например, вот как будет выглядеть настройка в school-ui project/| bootstrap.yml :

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:https://username:password@localhost:8761/eureka}

Вам нужно будет внести аналогичные изменения в URL-адреса в docker-compose.yml .

Расширьте свои знания о микросервисах Spring, Docker и OAuth 2.0

В этом руководстве показано, как обеспечить безопасность взаимодействия между службами в архитектуре микросервисов. Вы узнали, как использовать HTTPS везде и блокировать свой API с помощью OAuth 2.0 и JWTs.

Вы можете найти исходный код этого примера на GitHub по адресу oktadeveloper/okta-spring-microservices-https-example .

Если вы хотите немного подробнее изучить эти темы, я думаю, вам понравятся следующие записи в блоге:

  • Создавайте микросервисы Spring и настраивайте их для производства

  • Создайте архитектуру микросервисов для микро-пивоварен с помощью Spring Boot

  • Создавайте и защищайте микросервисы с помощью Spring Boot 2.0 и OAuth 2.0

  • Разработка архитектуры микросервисов с использованием OAuth 2.0 и JHipster

  • Безопасная связь между серверами с помощью Spring Boot и OAuth 2.0

Эти записи в блоге помогли заставить все работать в этом посте:

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

Чтобы получать уведомления о новых публикациях в нашем блоге, посвященных технологиям, следуйте за нами @oktadev в Twitter или подписывайтесь на наш канал YouTube .

Оригинал: “https://dev.to/oktadev/secure-service-to-service-spring-microservices-with-https-and-oauth-2-0-2889”