Интеграция Spring Boot 2 + OAuth2 для автономного использования или использования микросервисов

В этом посте я попытаюсь продемонстрировать чистую и простую интеграцию Spring Boot 2 с OAuth2.

В этом простом, но исчерпывающем примере / руководстве я постараюсь обеспечить чистую интеграцию замечательной Spring Boot 2 вместе со спецификацией OAuth2. К счастью, Spring проделала за нас тяжелую работу и щедро предоставила все обязательные библиотеки. Единственная сложная задача - собрать все воедино и заставить их работать в абсолютной гармонии. Результатом этого поста является создание сервиса, который можно использовать в автономном режиме или в сотрудничестве с другими сервисами в архитектуре микросервисов. Исходный код этого примера можно найти на github. Найдите ссылку в конце этого сообщения.

Я намерен сохранить всю реализацию как можно более чистой, чтобы каждый разработчик мог настроить ее в соответствии со своими потребностями. Не забывайте, это полностью рабочий пример, почти готовый к использованию в вашей инфраструктуре. Я предполагаю, что вы уже знакомы с основными компонентами и терминологией спецификации OAuth2, поскольку они будут использоваться в этом примере. Чтобы быстро или полностью освежиться, просто перейдите по этой ссылке.

Чтобы завершить это руководство, мы будем работать с некоторыми из широко используемых инструментов с точки зрения разработки программного обеспечения. Найдите ниже полный список. Однако Spring Boot и OAuth2 не включены.

  • Maven
  • PostgreSQL
  • Спящий режим
  • Весенняя безопасность
  • Весенние данные
  • Пролетный путь

Итак, приступим!

Конфигурация

Теперь давайте продолжим и соберем все подходящие библиотеки. В каждом разделе дается краткое объяснение важнейших частей, но вы можете поискать в Интернете дополнительные пояснения.

Конфигурация POM

Для начала нам нужно будет соответствующим образом настроить наш POM и включить все зависимости, как показано ниже.

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>in your projects or in y

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.3.5</version>
        </dependency>
    </dependencies>

Как показано выше, нам понадобится пара зависимостей Spring Boot и Cloud. Кроме того, мы включаем зависимости, чтобы мы могли взаимодействовать с базой данных PostgreSQL.

Конфигурация пружины

Прежде всего, нам нужно указать Spring сканировать все пакеты на предмет классов на основе аннотаций, чтобы они были доступны для инъекций. По умолчанию Spring Boot будет сканировать все пакеты, начиная с root, так что это хороший способ, если мы хотим уменьшить объем сканирования. Кроме того, мы устанавливаем путь к объектам репозитория. Подробнее о repository классах в следующих разделах.

<context:component-scan base-package="com.petroskovatsis.examples.springboot2andoauth2"/>
<jpa:repositories base-package="com.petroskovatsis.examples.springboot2andoauth2.repository"/>

Затем нам нужно настроить источник данных и соответствующим образом назначить его диспетчеру сущностей Hibernate. Обратите внимание, как мы устанавливаем путь к нашим объектам домена для сканирования Hibernate в атрибуте packagesToScan.

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"
      p:driverClassName="${settings.jdbc.driverClassName}"
      p:url="${settings.jdbc.url}"
      p:username="${settings.jdbc.username}"
      p:password="${settings.jdbc.password}"
      p:initialSize="${settings.jdbc.initial_size}"
      p:maxTotal="${settings.jdbc.max_total}"
      p:maxIdle="${settings.jdbc.max_idle}"
      p:minIdle="${settings.jdbc.min_idle}"
      p:testOnBorrow="true"
      p:validationQuery="select 1"/>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan">
        <list>
            <value>com.petroskovatsis.examples.springboot2andoauth2.domain</value>
        </list>
    </property>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">${settings.hibernate.dialect}</prop>
            <prop key="hibernate.show_sql">${settings.hibernate.show_sql}</prop>
            <prop key="hibernate.format_sql">${settings.hibernate.format_sql}</prop>
        </props>
    </property>
</bean>

Все значения settings.* находятся в нашем application.properties файле в папке resources. Это путь к папке по умолчанию, используемый Spring Boot для чтения и загрузки любой стартовой и начальной информации.

Конфигурация безопасности

Наконец, мы завершаем наш этап настройки захватывающей частью безопасности.

Имейте в виду, что это реализация all-in-one. Это означает, что наша служба будет действовать как сервер authentication и authorization, а также как сервер resource. В качестве альтернативы и, возможно, лучшего дизайна, мы можем оставить эту службу исключительно в качестве сервера авторизации и аутентификации и перенести все манипуляции с учетной записью в другую службу. Для простоты мы сгруппировали их вместе, чтобы продемонстрировать непрерывный пример.

Шаг 1. Представьте класс WebSecurityConfig. Мы соответствующим образом аннотируем этот класс и расширяем класс конфигурации Spring, как показано ниже. Здесь важно то, что мы используем некоторые полезности, уже имеющиеся в Spring, такие как AuthenticationManager.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

Шаг 2. Представьте класс AuthorizationServerConfig. Мы используем аннотацию @EnableAuthorizationServer, чтобы обозначить этот сервер авторизации и позволить Spring творить чудеса.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
...

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

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient(stargateClientId)
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("Create", "Read", "Update", "Delete", "Auth:Logout")
            .accessTokenValiditySeconds(36000)
            .refreshTokenValiditySeconds(86400);
}

Шаг 3. Представьте ResourceServerConfig. То же самое и здесь, нам нужно сообщить Spring, что это также сервер ресурсов, добавив аннотацию @EnableResourceServer.

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

Еще одна важная часть - это настройка безопасности и разрешений наших конечных точек. Нам нужно будет разрешить /oauth/token и /account, чтобы мы могли войти в систему и создать учетную запись. Все остальные запросы должны быть защищены, если вы не предоставите правильный токен.

@Override
public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers(HttpMethod.POST, "/oauth/token", "/account").permitAll()
            .anyRequest().authenticated();
}

Реализация

Теперь перейдем к части реализации. Здесь нет необходимости в критической и обширной бизнес-логике. В большинстве случаев процесс аутентификации должен быть простым и понятным. Это то, чего мы здесь пытаемся достичь. Что касается процесса авторизации, нам не о чем беспокоиться, поскольку Spring делает всю эту сложную часть за нас. Вы помните @EnableAuthorizationServer, который мы использовали выше в разделе конфигурации? :)

Объекты домена

Нам просто понадобится простая сущность Account, которая позволит нам войти в систему и получить действующий токен доступа. Это очень, очень и очень простая реализация с использованием только имени пользователя и пароля. Расширьте его в соответствии со своими потребностями!

@Entity
@Table(name = "ACCOUNT")
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "account_id")
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @ElementCollection(targetClass = Role.class, fetch = FetchType.LAZY)
    @CollectionTable(name = "ACCOUNT_ROLE", joinColumns = {@JoinColumn(name = "account_id")})
    @Enumerated(EnumType.STRING)
    @Column(name = "account_role")
    private Set<Role> roles;

Класс Repository

Для класса репозитория мы добавляем соответствующую аннотацию @Repository и расширяем класс JpaRepository, который включен в библиотеку Spring Data JPA. Расширение этого класса включает методы, готовые к использованию и успешно взаимодействующие с базой данных.

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {

    Optional<Account> findByUsername(String username);
}

Класс контроллера

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

@RestController
@RequestMapping("/account")
public class AccountControllerImpl implements AccountController {


    @Override
    @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<AccountDto> handleCreateRequest(@RequestBody AccountCreateRequest request) {
        AccountDto response = accountService.createAccount(request);
        return new ResponseEntity(response, HttpStatus.CREATED);
    }
}

Скрипты пролетного пути

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

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

Запустить и протестировать службу

[Барабаны] Да, мы добрались до конца! Пришло время запустить и протестировать службу.

Запустить базу данных

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

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

Создайте и запустите службу

Поскольку мы используем maven для упаковки и сборки всего приложения, простой строки более чем достаточно, чтобы выполнить эту работу за нас.

mvn clean install

Целевая папка будет создана, и банка будет лежать в ней. Все, что нам нужно сделать сейчас, это запустить приложение либо из командной строки, как показано ниже, либо из нашей любимой IDE.

java -jar target/spring-boot-2-and-oauth2-1.0.0.jar

И вуаля!

Ваша служба должна быть запущена и доступна через порт 8080.

Тест с бессонницей

Теперь давайте проверим конечные точки. В папке utils находится json-файл Insomnia. Insomnia - это клиент REST, который можно скачать здесь. Теперь вы можете импортировать файл json и получить что-то вроде этого:

Идите вперед и проверьте все эти конечные точки!

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

Да, еще вы можете клонировать проект с github.

Ваше здоровье!