我有一个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
答案 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);
}
}