如何将Spring安全上下文传播到JMS?

时间:2013-09-24 09:48:27

标签: spring-security jms

我有一个Web应用程序,它通过弹簧过滤器设置弹簧安全上下文。服务受基于用户角色的弹簧注释的保护。这很有效。

异步任务在JMS侦听器中执行(扩展javax.jms.MessageListener)。这个监听器的设置是用Spring完成的。

在此时,用户通过身份验证,从Web应用程序发送消息。在消息处理期间,我需要在JMS线程(用户和角色)中进行相同的身份验证。

今天,这是通过将Spring身份验证放在JMS ObjectMessage中来完成的:

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

然后在JMS侦听器内部提取身份验证对象并在上下文中设置:

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

大部分时间都可以使用。但是,如果在处理消息之前有延迟,则永远不会处理消息。我无法确定这些消息丢失的原因,但我不确定我们传播身份验证的方式是好的,即使它在另一台服务器上处理消息时在custer中工作。

这是传播spring身份验证的正确方法吗?

此致 的Mickaël

3 个答案:

答案 0 :(得分:2)

我没有找到更好的解决方案,但这个对我来说很好。

通过发送JMS消息,我将Authentication存储为Header,并分别通过接收重新创建的安全上下文。要将Authentication存储为标题,您必须将其序列化为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;
  }
}

通过发送刚刚设置的消息标题 - 您可以为消息模板创建装饰器,以便它自动发生。你的装饰师只需要调用这样的方法:

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

接收变得更复杂,但也可以自动完成。而不是应用@EnableJMS使用以下配置:

@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);
    }     

}

答案 1 :(得分:1)

我为自己实施了一个不同的解决方案,对我来说似乎更容易。

我已经有了一个消息转换器,标准的JSON Jackson消息转换器,我需要在JMSTemplate和监听器上配置。

所以我创建了一个MessageConverter实现,它包装了另一个消息转换器,并通过JMS消息属性传播安全上下文。 (在我的例子中,传播的上下文是一个JWT令牌,我可以从当前上下文中提取并应用于侦听线程的安全上下文)。

这样,安全上下文传播的全部责任在一个类中优雅地实现,并且只需要一点点配置。

答案 2 :(得分:0)

非常感谢,但是我正在轻松地解决这一问题。放入一个util文件并解决。

public class AuthenticationSerializerUtil {
public static final String AUTH_CONTEXT = "authContext";

public static String serialize(Authentication authentication) {
    byte[] bytes = SerializationUtils.serialize(authentication);
    return DatatypeConverter.printBase64Binary(bytes);
}

public static Authentication deserialize(String authentication) {
    byte[] decoded = DatatypeConverter.parseBase64Binary(authentication);
    Authentication auth = (Authentication) SerializationUtils.deserialize(decoded);
    return auth;
}

/**
 * taking message and return string json from message & set current context
 * @param message
 * @return
 */
public static String jsonAndSetContext(Message message){
    LongString authContext = (LongString)message.getMessageProperties().getHeaders().get(AUTH_CONTEXT);
    Authentication auth = deserialize(authContext.toString());
    SecurityContextHolder.getContext().setAuthentication(auth);
    byte json[] = message.getBody();
    return new String(json);

}

}