[tlinh2110-BAEL1382] Add Security in SI (#3593)
* [tlinh2110-BAEL1382] Add Security in SI * [tlinh2110-BAEL1382] Upgrade to Spring 5 & add Logger
This commit is contained in:
parent
372cba10bb
commit
56316fd029
|
@ -18,7 +18,7 @@
|
|||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring.integration.version>4.3.5.RELEASE</spring.integration.version>
|
||||
<spring.version>5.0.1.RELEASE</spring.version>
|
||||
<spring-social.version>1.1.4.RELEASE</spring-social.version>
|
||||
<javax-mail.version>1.4.7</javax-mail.version>
|
||||
<javax-activation.version>1.1.1</javax-activation.version>
|
||||
|
@ -68,7 +68,7 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-core</artifactId>
|
||||
<version>${spring.integration.version}</version>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.activation</groupId>
|
||||
|
@ -84,17 +84,17 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-twitter</artifactId>
|
||||
<version>${spring.integration.version}</version>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-mail</artifactId>
|
||||
<version>${spring.integration.version}</version>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-ftp</artifactId>
|
||||
<version>${spring.integration.version}</version>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.social</groupId>
|
||||
|
@ -104,7 +104,36 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-file</artifactId>
|
||||
<version>${spring.integration.version}</version>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-security</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.si.security;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MessageConsumer {
|
||||
|
||||
private String messageContent;
|
||||
|
||||
private Map<String, String> messagePSContent = new ConcurrentHashMap<>();
|
||||
|
||||
public String getMessageContent() {
|
||||
return messageContent;
|
||||
}
|
||||
|
||||
public void setMessageContent(String messageContent) {
|
||||
this.messageContent = messageContent;
|
||||
}
|
||||
|
||||
public Map<String, String> getMessagePSContent() {
|
||||
return messagePSContent;
|
||||
}
|
||||
|
||||
public void setMessagePSContent(Map<String, String> messagePSContent) {
|
||||
this.messagePSContent = messagePSContent;
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "endDirectChannel")
|
||||
public void endDirectFlow(Message<?> message) {
|
||||
setMessageContent(message.getPayload().toString());
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "finalPSResult")
|
||||
public void endPSFlow(Message<?> message) {
|
||||
Logger.getAnonymousLogger().info(Thread.currentThread().getName() + " has completed ---------------------------");
|
||||
messagePSContent.put(Thread.currentThread().getName(), (String) message.getPayload());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.baeldung.si.security;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.integration.security.channel.SecuredChannel;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public class SecuredDirectChannel {
|
||||
|
||||
@Bean(name = "startDirectChannel")
|
||||
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = { "ROLE_VIEWER", "jane" })
|
||||
public DirectChannel startDirectChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@ServiceActivator(inputChannel = "startDirectChannel", outputChannel = "endDirectChannel")
|
||||
@PreAuthorize("hasRole('ROLE_LOGGER')")
|
||||
public Message<?> logMessage(Message<?> message) {
|
||||
Logger.getAnonymousLogger().info(message.toString());
|
||||
return message;
|
||||
}
|
||||
|
||||
@Bean(name = "endDirectChannel")
|
||||
@SecuredChannel(interceptor = "channelSecurityInterceptor", sendAccess = { "ROLE_EDITOR" })
|
||||
public DirectChannel endDirectChannel() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Bean
|
||||
public ChannelSecurityInterceptor channelSecurityInterceptor(AuthenticationManager authenticationManager, AccessDecisionManager customAccessDecisionManager) {
|
||||
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
|
||||
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
|
||||
channelSecurityInterceptor.setAccessDecisionManager(customAccessDecisionManager);
|
||||
return channelSecurityInterceptor;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.baeldung.si.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.vote.AffirmativeBased;
|
||||
import org.springframework.security.access.vote.RoleVoter;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager() throws Exception {
|
||||
return super.authenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AccessDecisionManager customAccessDecisionManager() {
|
||||
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
|
||||
decisionVoters.add(new RoleVoter());
|
||||
decisionVoters.add(new UsernameAccessDecisionVoter());
|
||||
AccessDecisionManager accessDecisionManager = new AffirmativeBased(decisionVoters);
|
||||
return accessDecisionManager;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Bean
|
||||
public ChannelSecurityInterceptor channelSecurityInterceptor(AuthenticationManager authenticationManager, AccessDecisionManager customAccessDecisionManager) {
|
||||
ChannelSecurityInterceptor channelSecurityInterceptor = new ChannelSecurityInterceptor();
|
||||
channelSecurityInterceptor.setAuthenticationManager(authenticationManager);
|
||||
channelSecurityInterceptor.setAccessDecisionManager(customAccessDecisionManager);
|
||||
return channelSecurityInterceptor;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package com.baeldung.si.security;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.PublishSubscribeChannel;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.integration.config.GlobalChannelInterceptor;
|
||||
import org.springframework.integration.security.channel.SecuredChannel;
|
||||
import org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
public class SecurityPubSubChannel {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Bean(name = "finalPSResult")
|
||||
public DirectChannel finalPSResult() {
|
||||
return new DirectChannel();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@GlobalChannelInterceptor(patterns = { "startPSChannel", "endDirectChannel" })
|
||||
public ChannelInterceptor securityContextPropagationInterceptor() {
|
||||
return new SecurityContextPropagationChannelInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ThreadPoolTaskExecutor executor() {
|
||||
ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
|
||||
pool.setCorePoolSize(10);
|
||||
pool.setMaxPoolSize(10);
|
||||
pool.setWaitForTasksToCompleteOnShutdown(true);
|
||||
return pool;
|
||||
}
|
||||
|
||||
public String getRoles() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return securityContext.getAuthentication().getAuthorities().stream().map(auth -> auth.getAuthority()).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
return securityContext.getAuthentication().getName();
|
||||
}
|
||||
|
||||
public Message<String> buildNewMessage(String content, Message<?> message) {
|
||||
DefaultMessageBuilderFactory builderFactory = new DefaultMessageBuilderFactory();
|
||||
MessageBuilder<String> messageBuilder = builderFactory.withPayload(content);
|
||||
messageBuilder.copyHeaders(message.getHeaders());
|
||||
return messageBuilder.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.si.security;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
public class UsernameAccessDecisionVoter implements AccessDecisionVoter<Object> {
|
||||
private String rolePrefix = "ROLE_";
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
if ((attribute.getAttribute() != null)
|
||||
&& !attribute.getAttribute().startsWith(rolePrefix)) {
|
||||
return true;
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
|
||||
if (authentication == null) {
|
||||
return ACCESS_DENIED;
|
||||
}
|
||||
String name = authentication.getName();
|
||||
int result = ACCESS_ABSTAIN;
|
||||
for (ConfigAttribute attribute : attributes) {
|
||||
if (this.supports(attribute)) {
|
||||
result = ACCESS_DENIED;
|
||||
if (attribute.getAttribute().equals(name)) {
|
||||
return ACCESS_GRANTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.baeldung.si;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.baeldung.si.security.MessageConsumer;
|
||||
import com.baeldung.si.security.SecuredDirectChannel;
|
||||
import com.baeldung.si.security.SecurityConfig;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { SecurityConfig.class, SecuredDirectChannel.class, MessageConsumer.class })
|
||||
public class TestSpringIntegrationSecurity {
|
||||
|
||||
@Autowired
|
||||
SubscribableChannel startDirectChannel;
|
||||
|
||||
@Autowired
|
||||
MessageConsumer messageConsumer;
|
||||
|
||||
final String DIRECT_CHANNEL_MESSAGE = "Direct channel message";
|
||||
|
||||
@Test(expected = AuthenticationCredentialsNotFoundException.class)
|
||||
public void givenNoUser_whenSendToDirectChannel_thenCredentialNotFound() {
|
||||
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
}
|
||||
|
||||
@Test(expected = AccessDeniedException.class)
|
||||
@WithMockUser(username = "jane", roles = { "LOGGER" })
|
||||
public void givenRoleLogger_whenSendMessageToDirectChannel_thenAccessDenied() throws Throwable {
|
||||
try {
|
||||
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
} catch (Exception e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = AccessDeniedException.class)
|
||||
@WithMockUser(username = "jane")
|
||||
public void givenJane_whenSendMessageToDirectChannel_thenAccessDenied() throws Throwable {
|
||||
try {
|
||||
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
} catch (Exception e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = AccessDeniedException.class)
|
||||
@WithMockUser(roles = { "VIEWER" })
|
||||
public void givenRoleViewer_whenSendToDirectChannel_thenAccessDenied() throws Throwable {
|
||||
try {
|
||||
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
} catch (Exception e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = { "LOGGER", "VIEWER", "EDITOR" })
|
||||
public void givenRoleLoggerAndUser_whenSendMessageToDirectChannel_thenFlowCompletedSuccessfully() {
|
||||
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
assertEquals(DIRECT_CHANNEL_MESSAGE, messageConsumer.getMessageContent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "jane", roles = { "LOGGER", "EDITOR" })
|
||||
public void givenJaneLoggerEditor_whenSendToDirectChannel_thenFlowCompleted() {
|
||||
startDirectChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
assertEquals(DIRECT_CHANNEL_MESSAGE, messageConsumer.getMessageContent());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package com.baeldung.si;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.baeldung.si.security.MessageConsumer;
|
||||
import com.baeldung.si.security.SecurityConfig;
|
||||
import com.baeldung.si.security.SecurityPubSubChannel;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { SecurityPubSubChannel.class, MessageConsumer.class, SecurityConfig.class })
|
||||
public class TestSpringIntegrationSecurityExecutor {
|
||||
|
||||
@Autowired
|
||||
SubscribableChannel startPSChannel;
|
||||
|
||||
@Autowired
|
||||
MessageConsumer messageConsumer;
|
||||
|
||||
@Autowired
|
||||
ThreadPoolTaskExecutor executor;
|
||||
|
||||
final String DIRECT_CHANNEL_MESSAGE = "Direct channel message";
|
||||
|
||||
@Before
|
||||
public void clearData() {
|
||||
messageConsumer.setMessagePSContent(new ConcurrentHashMap<>());
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "user", roles = { "VIEWER" })
|
||||
public void givenRoleUser_whenSendMessageToPSChannel_thenNoMessageArrived() throws IllegalStateException, InterruptedException {
|
||||
startPSChannel.send(new GenericMessage<String>(DIRECT_CHANNEL_MESSAGE));
|
||||
|
||||
executor.getThreadPoolExecutor().awaitTermination(2, TimeUnit.SECONDS);
|
||||
|
||||
assertEquals(1, messageConsumer.getMessagePSContent().size());
|
||||
assertTrue(messageConsumer.getMessagePSContent().values().contains("user"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "user", roles = { "LOGGER", "VIEWER" })
|
||||
public void givenRoleUserAndLogger_whenSendMessageToPSChannel_then2GetMessages() throws IllegalStateException, InterruptedException {
|
||||
startPSChannel.send(new GenericMessage<String>(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"));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue