问题
We currently have a number of Spring microservices that are communicating with REST endpoints and RabbitMQ queues. We have just implemented OAuth2 security on all of the services, and the REST endpoints are appropriately secured.
We have a library that we wrote which creates the RabbitTemplate and AmqpAdmin beans so that the boilerplate code doesn't have to be done in every service. We are connecting to the RabbitMQ server in Spring with a specific user for regular clients, and another for admins. We don't want to connect to the RabbitMQ server as the individual user.
Is it possible, if we pass the access token in the rabbit message header, to configure the RabbitTemplate to check the token before the message gets handled? Is this something that can/should be done in the AfterReceive/BeforePublish processors globally for the template? Or will this need to be checked individually in each listener method?
Thanks
回答1:
I was able to work out a solution by creating a custom MessageListenerContainerFactory and 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);
}
...
来源:https://stackoverflow.com/questions/50358936/oauth2-authorization-with-spring-security-and-rabbitmq