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

Spring security 如何将spring安全上下文传播到JMS?,spring-security,jms,Spring Security,Jms,我有一个web应用程序,它通过spring过滤器设置spring安全上下文。服务受到基于用户角色的spring注释的保护。这很有效 异步任务在JMS侦听器中执行(扩展javax.JMS.MessageListener)。此侦听器的设置是使用Spring完成的 消息从web应用程序发送,此时对用户进行身份验证。在消息处理期间,我需要在JMS线程(用户和角色)中进行相同的身份验证 今天,这是通过将spring身份验证放在JMS ObjectMessage中完成的: SecurityContext c

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

异步任务在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身份验证的正确方法吗

问候,,
米克尔

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

通过发送JMS消息,我将
身份验证
存储为标头,并分别通过接收重新创建的安全上下文。为了将
身份验证
存储为标头,必须将其序列化为
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;
  }
}
通过发送JustSetMessageHeader,您可以为消息模板创建Decorator,这样它就会自动发生。在decorator中,只需调用以下方法:

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

}

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

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

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


通过这种方式,安全上下文传播的整个职责在一个类中得到了优雅的实现,只需要一点配置。

非常感谢,但我用一种简单的方式处理这个问题。放置一个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);

    }
}

在这种情况下,我不熟悉Spring,但我以前遇到过这个问题。一个好的安全上下文将有一个过期时间,过期后将无法再使用。通常,如果消息传递延迟超过到期时间,则安全上下文将过期,并且消息未被处理。这可能是这里的问题所在,在这种情况下,增加到期时间可能会解决问题,或者至少会降低它的普遍性。