How to propagate spring security context in JMS?

I have a web application that sets spring security context through spring filter. Services are protected by spring annotations based on user roles. It works.

Asynchronous tasks are performed in JMS listeners (extension javax.jms.MessageListener). These listeners are configured using Spring.

Messages are sent from the web application, and the user is authenticated at this time. I need the same authentication in the JMS thread (user and roles) during message processing.

Today, this is done by placing spring authentication in a JMS ObjectMessage:

SecurityContext context = SecurityContextHolder.getContext(); Authentication auth = context.getAuthentication(); ... put the auth object in jms message object 

Then, inside the JMS listener, the authentication object is retrieved and set in the context:

 SecurityContext context = new SecurityContextImpl(); context.setAuthentication(auth); SecurityContextHolder.setContext(context); 

This works most of the time. But when there is a delay before processing the message, the message will never be processed. I could not determine the reason for the loss of these messages, but I'm not sure that we are distributing authentication, this is good even if it works in custer when the message is processed on another server.

Is this the correct way to distribute spring authentication?

Regards, Michael

+6
source share
2 answers

I have implemented for myself another solution that seems easier to me.

I already have a message converter, the standard JSON Jackson message converter, which I need to configure on JMSTemplate and listeners.

So, I created a MessageConverter implementation that wraps around another message converter and propagates the security context through the JMS message properties. (In my case, the distributed context is a JWT token, which I can extract from the current context and apply to the security context of the listening stream).

Thus, all responsibility for disseminating the security context is elegantly implemented in one class and requires only a little configuration.

+1
source

I did not find a better solution, but it works just fine for me.

By sending a JMS message, I save Authentication as a header and, accordingly, getting a recreation of the Security Context. To save Authentication as a header, you must serialize it as Base64 :

 class AuthenticationSerializer { static String serialize(Authentication authentication) { byte[] bytes = SerializationUtils.serialize(authentication); return DatatypeConverter.printBase64Binary(bytes); } static Authentication deserialize(String authentication) { byte[] decoded = DatatypeConverter.parseBase64Binary(authentication); Authentication auth = (Authentication) SerializationUtils.deserialize(decoded); return auth; } } 

By sending the message header you just set, you can create a Decorator for the message template so that it happens automatically. In you decorator just call this method:

 private void attachAuthenticationContext(Message message){ Authentication auth = SecurityContextHolder.getContext().getAuthentication(); String serialized = AuthenticationSerializer.serialize(auth); message.setStringProperty("authcontext", serialized); } 

Getting more complicated, but it can also be done automatically. Instead of using @EnableJMS use the following configuration:

 @Configuration class JmsBootstrapConfiguration { @Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() { return new JmsListenerPostProcessor(); } @Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME) public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() { return new JmsListenerEndpointRegistry(); } } class JmsListenerPostProcessor extends JmsListenerAnnotationBeanPostProcessor { @Override protected MethodJmsListenerEndpoint createMethodJmsListenerEndpoint() { return new ListenerEndpoint(); } } class ListenerEndpoint extends MethodJmsListenerEndpoint { @Override protected MessagingMessageListenerAdapter createMessageListenerInstance() { return new ListenerAdapter(); } } class ListenerAdapter extends MessagingMessageListenerAdapter { @Override public void onMessage(Message jmsMessage, Session session) throws JMSException { propagateSecurityContext(jmsMessage); super.onMessage(jmsMessage, session); } private void propagateSecurityContext(Message jmsMessage) throws JMSException { String authStr = jmsMessage.getStringProperty("authcontext"); Authentication auth = AuthenticationSerializer.deserialize(authStr); SecurityContextHolder.getContext().setAuthentication(auth); } } 
0
source

Source: https://habr.com/ru/post/954487/


All Articles