Spring security 如何将spring安全上下文传播到JMS?
我有一个web应用程序,它通过spring过滤器设置spring安全上下文。服务受到基于用户角色的spring注释的保护。这很有效 异步任务在JMS侦听器中执行(扩展javax.JMS.MessageListener)。此侦听器的设置是使用Spring完成的 消息从web应用程序发送,此时对用户进行身份验证。在消息处理期间,我需要在JMS线程(用户和角色)中进行相同的身份验证 今天,这是通过将spring身份验证放在JMS ObjectMessage中完成的: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
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,但我以前遇到过这个问题。一个好的安全上下文将有一个过期时间,过期后将无法再使用。通常,如果消息传递延迟超过到期时间,则安全上下文将过期,并且消息未被处理。这可能是这里的问题所在,在这种情况下,增加到期时间可能会解决问题,或者至少会降低它的普遍性。