我们目前有许多Spring微服务与REST端点和RabbitMQ队列进行通信。我们刚刚在所有服务上实现了OAuth2安全性,并且REST端点得到了适当的保护。
我们编写了一个库,它创建了RabbitTemplate和AmqpAdmin bean,因此不必在每个服务中完成样板代码。我们在Spring中连接到RabbitMQ服务器,特定用户为常规客户端,另一个用于管理员。我们不希望以个人用户身份连接到RabbitMQ服务器。
如果我们在Rabbit消息头中传递访问令牌,是否有可能将RabbitTemplate配置为在处理消息之前检查令牌?这是否可以/应该在全局的AfterReceive / BeforePublish处理器中为模板完成?或者是否需要在每个监听方法中单独检查?
由于
答案 0 :(得分:3)
我能够通过创建自定义MessageListenerContainerFactory和MessageListenerContainer来解决问题。
CustomMessageListenerContainerFactory.java:
public class CustomMessageListenerContainerFactory extends AbstractRabbitListenerContainerFactory {
DefaultTokenServices tokenServices;
public CustomMessageListenerContainerFactory(DefaultTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
/**
* Create an empty container instance.
*
* @return the new container instance.
*/
@Override
protected CustomMessageListenerContainer createContainerInstance() {
return new CustomMessageListenerContainer(tokenServices);
}
}
CustomMessageListenerContainer.java:
public class CustomMessageListenerContainer extends SimpleMessageListenerContainer {
private final static String errorMessage = "No valid credentials found in request: {}";
private final static String handlingMessage = "Handling queue: {}";
private final static String receivedMessage = "Received Message: {}";
private final static Logger logger = LoggerFactory.getLogger(CustomMessageListenerContainer.class);
private DefaultTokenServices tokenServices;
/**
* Constructor
*
* @param tokenServices The instance of DefaultTokenServices used to decrypt the access token.
*/
public CustomMessageListenerContainer(DefaultTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
/**
* This method checks to see if there is a valid authorization
*
* @param channel The AMQP channel on which the message was published.
* @param messageIn The incoming message.
* @throws Exception Throws an exception when there are no valid credentials in the message.
*/
@Override
protected void executeListener(Channel channel, Message messageIn) throws Exception {
logger.info(handlingMessage, (Object[]) getQueueNames());
logger.info(receivedMessage, BeanUtils.beanProperties(messageIn));
if (messageIn.getMessageProperties().getHeaders().keySet().stream().anyMatch(t -> Objects.equals(t.toLowerCase(), "authorization"))) {
String accessKey = messageIn.getMessageProperties()
.getHeaders()
.keySet()
.stream()
.filter(t -> Objects.equals(t.toLowerCase(), "authorization"))
.findFirst()
.get();
OAuth2Authentication auth = tokenServices.loadAuthentication(messageIn.getMessageProperties().getHeaders().get(accessKey).toString());
// If the token is expired, there will be no auth.
if (auth != null) {
SecurityContextHolder.getContext().setAuthentication(auth);
super.executeListener(channel, messageIn);
return;
}
}
rejectMessage(channel, messageIn);
}
private void rejectMessage(Channel channel, Message messageIn) throws Exception {
logger.info(errorMessage, (Object[]) getQueueNames());
String localMessage = errorMessage.replace("{}", String.join(", ", getQueueNames()));
if (messageIn.getMessageProperties().getReplyTo() != null) {
channel.basicPublish("",
messageIn.getMessageProperties().getReplyTo(),
new AMQP.BasicProperties.Builder()
.contentType("application/json")
.correlationId(messageIn.getMessageProperties().getCorrelationId())
.build(),
"{\"errorMessage\":\"".concat(localMessage).concat("\"}").getBytes());
}
throw new AmqpRejectAndDontRequeueException(localMessage);
}
}
CustomRabbitListenerConfigurer.java:
...
@Override
public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
CustomMessageListenerContainerfactory factory = new CustomMessageListenerContainerfactory(tokenServices);
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
factory.setConnectionFactory(connectionFactory);
registrar.setContainerFactory(factory);
}
...