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

Безопасность В Весенней Интеграции

Узнайте, как использовать Spring Integration и Spring Security вместе в потоке интеграции.

Автор оригинала: baeldung.

1. введение

В этой статье мы сосредоточимся на том, как мы можем использовать Spring Integration и Spring Security вместе в потоке интеграции.

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

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

2. Конфигурация Интеграции Пружин

2.1. Зависимости

Во-первых, , нам нужно добавить зависимости интеграции Spring в наш проект.

Поскольку мы настроим простые потоки сообщений с Прямым каналом , PublishSubscribeChannel и ServiceActivator, нам нужна spring-integration-core зависимость.

Кроме того, нам также нужна зависимость spring-integration-security , чтобы иметь возможность использовать Spring Security в Spring Integration:


    org.springframework.integration
    spring-integration-security
    5.0.3.RELEASE

И мы также используем Spring Security, поэтому мы добавим spring-security-config в наш проект:


    org.springframework.security
    spring-security-config
    5.0.3.RELEASE

Мы можем проверить последнюю версию всех вышеперечисленных зависимостей в Maven Central: |/spring-integration-security , spring-security-config .

2.2. Конфигурация на Основе Java

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

@Configuration
@EnableIntegration
public class SecuredDirectChannel {
    //...
}

3. Защищенный канал сообщений

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

@Autowired
@Bean
public ChannelSecurityInterceptor channelSecurityInterceptor(
  AuthenticationManager authenticationManager, 
  AccessDecisionManager customAccessDecisionManager) {

    ChannelSecurityInterceptor 
      channelSecurityInterceptor = new ChannelSecurityInterceptor();

    channelSecurityInterceptor
      .setAuthenticationManager(authenticationManager);

    channelSecurityInterceptor
      .setAccessDecisionManager(customAccessDecisionManager);

    return channelSecurityInterceptor;
}

Компоненты AuthenticationManager и AccessDecisionManager определяются как:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {

    @Override
    @Bean
    public AuthenticationManager 
      authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public AccessDecisionManager customAccessDecisionManager() {
        List> 
          decisionVoters = new ArrayList<>();
        decisionVoters.add(new RoleVoter());
        decisionVoters.add(new UsernameAccessDecisionVoter());
        AccessDecisionManager accessDecisionManager
          = new AffirmativeBased(decisionVoters);
        return accessDecisionManager;
    }
}

Здесь мы используем два AccessDecisionVoter : RoleVoter и пользовательский Username AccessDecisionVoter.

Теперь мы можем использовать этот ChannelSecurityInterceptor для защиты нашего канала. Что нам нужно сделать, так это украсить канал @SecureChannel аннотацией:

@Bean(name = "startDirectChannel")
@SecuredChannel(
  interceptor = "channelSecurityInterceptor", 
  sendAccess = { "ROLE_VIEWER","jane" })
public DirectChannel startDirectChannel() {
    return new DirectChannel();
}

@Bean(name = "endDirectChannel")
@SecuredChannel(
  interceptor = "channelSecurityInterceptor", 
  sendAccess = {"ROLE_EDITOR"})
public DirectChannel endDirectChannel() {
    return new DirectChannel();
}

@SecureChannel принимает три свойства:

  • Свойство interceptor : относится к компоненту ChannelSecurityInterceptor|/. Свойства
  • send Access и receive Access : содержит политику для вызова send или receive действия на канале.

В приведенном выше примере мы ожидаем, что только пользователи, у которых есть ROLE_VIEWER или есть имя пользователя jane , могут отправить сообщение с канала startDirectChannel .

Кроме того, только пользователи, у которых есть ROLE_EDITOR , могут отправить сообщение на конечный прямой канал .

Мы достигаем этого при поддержке нашего пользовательского AccessDecisionManager: либо RoleVoter или UsernameAccessDecisionVoter возвращает утвердительный ответ, доступ предоставлен.

4. Активатор Защищенного сервиса

Стоит отметить, что мы также можем обеспечить безопасность нашего активатора Service/| методом Spring. Поэтому нам необходимо включить аннотацию безопасности метода:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
    //....
}

Для простоты в этой статье мы будем использовать только аннотации Spring pre и post , поэтому мы добавим аннотацию @enableglobalmetodsecurity в наш класс конфигурации и установим prePostEnabled в true .

Теперь мы можем защитить наш Активатор услуг с помощью @PreAuthorization аннотации:

@ServiceActivator(
  inputChannel = "startDirectChannel", 
  outputChannel = "endDirectChannel")
@PreAuthorize("hasRole('ROLE_LOGGER')")
public Message logMessage(Message message) {
    Logger.getAnonymousLogger().info(message.toString());
    return message;
}

Активатор Службы здесь получает сообщение из start Direct Channel и выводит сообщение в end Direct Channel .

Кроме того, метод доступен только в том случае, если текущий Аутентификация принципал имеет роль ROLE_LOGGER .

5. Распространение контекста безопасности

Spring Контекст безопасности по умолчанию привязан к потоку . Это означает, что контекст безопасности не будет распространяться на дочерний поток.

Для всех приведенных выше примеров мы используем как DirectChannel , так и ServiceActivator – которые все выполняются в одном потоке; таким образом, SecurityContext доступен на протяжении всего потока.

Однако при использовании Канала очереди , ExecutorChannel и Канала подписки на публикацию с Исполнителем сообщения будут передаваться из одного потока в другие потоки . В этом случае нам нужно распространить контекст безопасности на все потоки, получающие сообщения.

Давайте создадим еще один поток сообщений, который начинается с PublishSubscribeChannel канала, и два Активатора службы подписываются на этот канал:

@Bean(name = "startPSChannel")
@SecuredChannel(
  interceptor = "channelSecurityInterceptor", 
  sendAccess = "ROLE_VIEWER")
public PublishSubscribeChannel startChannel() {
    return new PublishSubscribeChannel(executor());
}

@ServiceActivator(
  inputChannel = "startPSChannel", 
  outputChannel = "finalPSResult")
@PreAuthorize("hasRole('ROLE_LOGGER')")
public Message changeMessageToRole(Message message) {
    return buildNewMessage(getRoles(), message);
}

@ServiceActivator(
  inputChannel = "startPSChannel", 
  outputChannel = "finalPSResult")
@PreAuthorize("hasRole('ROLE_VIEWER')")
public Message changeMessageToUserName(Message message) {
    return buildNewMessage(getUsername(), message);
}

В приведенном выше примере у нас есть два Активатора службы подписаться на канал start PS. Каналу требуется Аутентификация принципал с ролью ROLE_VIEWER , чтобы иметь возможность отправить ему сообщение.

Аналогично, мы можем вызвать сообщение change на роль service только в том случае, если Authentication principal имеет роль ROLE_LOGGER .

Кроме того, сообщение change На имя пользователя service может быть вызвано только в том случае, если Аутентификация принципал имеет роль ROLE_VIEWER .

Между тем, канал start PS будет работать при поддержке ThreadPoolTaskExecutor:

@Bean
public ThreadPoolTaskExecutor executor() {
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(10);
    pool.setMaxPoolSize(10);
    pool.setWaitForTasksToCompleteOnShutdown(true);
    return pool;
}

Следовательно, два Активатора служб будут выполняться в двух разных потоках. Чтобы распространить контекст безопасности на эти потоки, нам нужно добавить в наш канал сообщений SecurityContextPropagationChannelInterceptor :

@Bean
@GlobalChannelInterceptor(patterns = { "startPSChannel" })
public ChannelInterceptor securityContextPropagationInterceptor() {
    return new SecurityContextPropagationChannelInterceptor();
}

Обратите внимание, как мы украсили перехватчик канала распространения контекста безопасности аннотацией @GlobalChannelInterceptor|/. Мы также добавили наш канал start PS в его свойство patterns .

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

6. Тестирование

Давайте начнем проверять наши потоки сообщений с помощью некоторых тестов JUnit.

6.1. Зависимость

Нам, конечно, нужна spring-security-test зависимость на данный момент:


    org.springframework.security
    spring-security-test
    5.0.3.RELEASE
    test

Аналогично, последнюю версию можно проверить в Maven Central: spring-security-test .

6.2. Проверка Защищенного Канала

Во-первых, мы пытаемся отправить сообщение на наш прямой канал start:

@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void 
  givenNoUser_whenSendToDirectChannel_thenCredentialNotFound() {

    startDirectChannel
      .send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));
}

Поскольку канал защищен, мы ожидаем AuthenticationCredentialsNotFoundException исключение при отправке сообщения без предоставления объекта аутентификации.

Затем мы предоставляем пользователю, у которого есть роль ROLE_VIEWER, и отправляем сообщение на наш прямой канал запуска :

@Test
@WithMockUser(roles = { "VIEWER" })
public void 
  givenRoleViewer_whenSendToDirectChannel_thenAccessDenied() {
    expectedException.expectCause
      (IsInstanceOf. instanceOf(AccessDeniedException.class));

    startDirectChannel
      .send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));
 }

Теперь, хотя наш пользователь может отправить сообщение в start Direct Channel , потому что у него есть роль ROLE_VIEWER , но он не может вызвать службу LogMessage , которая запрашивает пользователя с ролью ROLE_LOGGER .

В этом случае будет вызвано исключение MessageHandlingException , причиной которого является исключение Accessdeniedexception .

Тест выдаст Исключение MessageHandlingException с причиной Исключение отказа в доступе . Следовательно, мы используем экземпляр правила ExpectedException для проверки исключения причины.

Затем мы предоставляем пользователю имя пользователя jane и две роли: ROLE_LOGGER и ROLE_EDITOR.

Затем попробуйте отправить сообщение на запустить прямой канал снова :

@Test
@WithMockUser(username = "jane", roles = { "LOGGER", "EDITOR" })
public void 
  givenJaneLoggerEditor_whenSendToDirectChannel_thenFlowCompleted() {
    startDirectChannel
      .send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));
    assertEquals
      (DIRECT_CHANNEL_MESSAGE, messageConsumer.getMessageContent());
}

Сообщение будет успешно перемещаться по всему нашему потоку, начиная с start Direct Channel до LogMessage activator, затем перейдите в endDirectChannel . Это связано с тем, что предоставленный объект аутентификации имеет все необходимые полномочия для доступа к этим компонентам.

6.3. Проверка распространения Контекста Безопасности

Перед объявлением тестового случая мы можем просмотреть весь поток нашего примера с помощью канала Publish Subscribe :

  • Поток начинается с канала start PS , который имеет доступ к политике send
  • Два Активатора службы подписаться на этот канал: у одного есть аннотация безопасности @PreAuthorize(“hasRole(‘ROLE_LOGGER’)”) , а у другого есть аннотация безопасности @PreAuthorize(“hasRole (‘ROLE_VIEWER’)”)

Итак, сначала мы предоставляем пользователю роль ROLE_VIEWER и пытаемся отправить сообщение на наш канал:

@Test
@WithMockUser(username = "user", roles = { "VIEWER" })
public void 
  givenRoleUser_whenSendMessageToPSChannel_thenNoMessageArrived() 
  throws IllegalStateException, InterruptedException {
 
    startPSChannel
      .send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));

    executor
      .getThreadPoolExecutor()
      .awaitTermination(2, TimeUnit.SECONDS);

    assertEquals(1, messageConsumer.getMessagePSContent().size());
    assertTrue(
      messageConsumer
      .getMessagePSContent().values().contains("user"));
}

Поскольку у нашего пользователя есть только роль ROLE_VIEWER , сообщение может проходить только через Канал запуска PS и один Активатор службы .

Следовательно, в конце потока мы получаем только одно сообщение.

Давайте предоставим пользователю обе роли ROLE_VIEWER и ROLE_LOGGER :

@Test
@WithMockUser(username = "user", roles = { "LOGGER", "VIEWER" })
public void 
  givenRoleUserAndLogger_whenSendMessageToPSChannel_then2GetMessages() 
  throws IllegalStateException, InterruptedException {
    startPSChannel
      .send(new GenericMessage(DIRECT_CHANNEL_MESSAGE));

    executor
      .getThreadPoolExecutor()
      .awaitTermination(2, TimeUnit.SECONDS);

    assertEquals(2, messageConsumer.getMessagePSContent().size());
    assertTrue
      (messageConsumer
      .getMessagePSContent()
      .values().contains("user"));
    assertTrue
      (messageConsumer
      .getMessagePSContent()
      .values().contains("ROLE_LOGGER,ROLE_VIEWER"));
}

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

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

В этом уроке мы рассмотрели возможность использования Spring Security в Spring Integration для защиты канала сообщений и ServiceActivator .

Как всегда, мы можем найти все примеры на Github .