简体   繁体   English

使用Spring Security和Rabbitmq进行OAuth2授权

[英]OAuth2 authorization with Spring Security and Rabbitmq

We currently have a number of Spring microservices that are communicating with REST endpoints and RabbitMQ queues. 我们目前有许多Spring微服务与REST端点和RabbitMQ队列进行通信。 We have just implemented OAuth2 security on all of the services, and the REST endpoints are appropriately secured. 我们刚刚在所有服务上实现了OAuth2安全性,并且REST端点得到了适当的保护。

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. 我们编写了一个库,它创建了RabbitTemplate和AmqpAdmin bean,因此不必在每个服务中完成样板代码。 We are connecting to the RabbitMQ server in Spring with a specific user for regular clients, and another for admins. 我们在Spring中连接到RabbitMQ服务器,特定用户为常规客户端,另一个用于管理员。 We don't want to connect to the RabbitMQ server as the individual user. 我们不希望以单个用户身份连接到RabbitMQ服务器。

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? 如果我们在Rabbit消息头中传递访问令牌,是否有可能将RabbitTemplate配置为在处理消息之前检查令牌? Is this something that can/should be done in the AfterReceive/BeforePublish processors globally for the template? 这是否可以/应该在全局的AfterReceive / BeforePublish处理器中为模板完成? Or will this need to be checked individually in each listener method? 或者是否需要在每个监听方法中单独检查?

Thanks 谢谢

I was able to work out a solution by creating a custom MessageListenerContainerFactory and MessageListenerContainer. 我能够通过创建自定义MessageListenerContainerFactory和MessageListenerContainer来解决问题。

CustomMessageListenerContainerFactory.java: 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: 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: 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);
    }
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM