6

我有一个通过弹簧过滤器设置弹簧安全上下文的 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 中工作。

这是传播弹簧认证的正确方法吗?

问候, 米凯尔

4

3 回答 3

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

}
于 2016-04-15T13:28:46.450 回答
1

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

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

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

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

于 2016-11-14T10:37:22.220 回答
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);

    }
}
于 2019-12-26T03:23:41.053 回答