Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/spring-boot/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 在Spring AMQP中检测凭据删除并从中恢复_Java_Spring Boot_Spring Amqp_Spring Cloud Config - Fatal编程技术网

Java 在Spring AMQP中检测凭据删除并从中恢复

Java 在Spring AMQP中检测凭据删除并从中恢复,java,spring-boot,spring-amqp,spring-cloud-config,Java,Spring Boot,Spring Amqp,Spring Cloud Config,我们有一个Spring云配置设置,使用Vault数据库后端(MySQL和RabbitMQ),这使我们能够将生成的凭据注入到属性中,例如: spring.rabbitmq.username spring.rabbitmq.password 当我们的应用程序启动时,我们有一组新的兔子凭证,并且我们能够根据需要请求一组新的凭证 由于我们的Rabbit凭据由Vault进行外部管理,因此它们可能在应用程序生命周期内的任何时间过期/删除(这也是一个弹性测试场景) 我的问题是我们如何(有效、可靠地):

我们有一个Spring云配置设置,使用Vault数据库后端(MySQL和RabbitMQ),这使我们能够将生成的凭据注入到属性中,例如:

  • spring.rabbitmq.username
  • spring.rabbitmq.password
当我们的应用程序启动时,我们有一组新的兔子凭证,并且我们能够根据需要请求一组新的凭证

由于我们的Rabbit凭据由Vault进行外部管理,因此它们可能在应用程序生命周期内的任何时间过期/删除(这也是一个弹性测试场景)

我的问题是我们如何(有效、可靠地):

  • 检测生成的凭据是否过期
  • 使用新凭据更新现有的Spring AMQP
    CachingConnectionFactory
我们的工作基础是,这需要完全由客户端处理,这是一个弹性问题,即使服务器愿意或能够发送到期通知

我们正在努力解决的是如何检测凭据过期,以便我们可以重新配置
CachingConnectionFactory

可能性包括:

  • 我们现在拥有的:一个
    ChannelListener
    ,它构建了所有新创建的
    频道
    s,并尝试在每个频道上创建/删除匿名
    队列
    每x秒,通过
    ShutdownListener
    监听任何
    shutdownlessignalException
    s 可能有403状态代码。这似乎是可行的,但有点复杂,我们已经看到并发性问题在shutdown处理程序中起到了非常重要的作用
  • 以某种方式钩住
    CachingConnectionFactory
    。我们尝试使用该类的一个克隆,但除了其复杂性之外,我们最终在创建队列时出现了
    RESOURCE\u LOCKED
    错误
  • 更简单、更轻量级的东西,例如,只需每隔x秒轮询代理,以验证当前凭据是否仍然存在
  • 部分问题在于
    访问被拒绝
    ——当
    CachingConnectionFactory
    尝试使用已删除的凭据时得到的结果——通常被视为致命的错误配置错误,而不是任何实际工作流程的一部分,或者可以从中恢复

    这里有一个优雅的解决方案吗


    使用:Spring Boot 1.5.10-RELEASE,Spring Cloud Dalston SR4


    更新:


    RabbitTemplate
    端,即使
    CachingConnectionFactory
    正确检测到对我发送到的交换机的
    访问被拒绝
    ,也不会引发异常(无论是否使用
    RetryTemplate

    配置为:

    spring
      rabbitmq:
        host: rabbitmq.service.consul
        port: 5672
        virtualHost: /
        template:
          retry:
            enabled: true
    
    代码是:

    @Autowired private RabbitTemplate rt;  // From RabbitAutoConfiguration
    
    @Bean
    public DirectExchange emailExchange() {
        return new DirectExchange("email");
    }
    
    public void sendEmail() {
        this.rt.send("email", "email.send", "test payload");
    }
    
    应用程序启动,声明
    电子邮件
    交换。RabbitMQ UI显示我的(生成的)用户和到exchange的连接,这在启动时是正常的。然后,我通过在运行本地测试调用上面的
    sendmail()
    email之前使用UI手动删除该用户来模拟凭据过期

    rabbitmplate
    调用不会引发异常或记录错误,但会记录以下(预期)错误:

    [AMQP连接127.0.0.1:5672]错误 o、 s.a.r.c.CachingConnectionFactory-通道关闭:通道错误; 协议方法:#方法(回复代码=403, 回复文本=访问被拒绝-访问vhost“/”中的exchange“电子邮件” 为用户拒绝 “cert-configserver-75c3ae60-da76-3058-e7df-a7b90ef72171”,类别id=60, 方法id=40)


    除了在所有
    rabbitmplate.send()
    调用之前检查凭据之外,我想知道是否有任何方法可以捕获发送过程中出现的
    访问被拒绝
    错误,以便我可以像对侦听器一样刷新凭据,并给
    RetryTemplate
    重试的机会。

    对于这种情况,侦听器容器会发出
    ListenerContainerConsumerFailedEvent
    。您可以听一听这个,检查它的
    原因
    和异常,然后决定
    停止()
    容器并执行您需要的其他操作。然后
    start()


    rabbitmplate
    端,只需要
    try…catch
    调用并分析异常,原因与此相同

    到目前为止,这不是我尝试过的,但这是我对如何处理
    访问被拒绝
    状态的最好感觉。从
    CachingConnectionFactory
    的角度来看,你真的不能在这件事上做任何事情

    更新

    我的申请如下:

    spring.rabbitmq.username=test
    spring.rabbitmq.password=test
    spring.rabbitmq.template.retry.enabled=true
    spring.rabbitmq.template.retry.initial-interval=1ms
    logging.level.org.springframework.retry=DEBUG
    

    这就是我为那个不存在的用户运行此应用程序时在控制台中的内容:

    Error during sending: ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile.
    
    更新2

    我的发现也是因为我们可以制作这些道具:

    spring.rabbitmq.publisher-confirms=true
    spring.rabbitmq.template.mandatory=true
    
    然后添加一个
    rabbitmplate.setConfirmCallback()
    ,我们被拒绝的异步发送消息将被拒绝。但是,它仍然是一个异步回调,类似于前面提到的
    ChannelListener
    。从Spring AMQP的角度来看,真的没有什么可做的。所有内容都是AMQP协议的异步特性,可能真的需要一些来自兔子客户端库的“快速失败”挂钩

    请在
    rabbitmq用户
    Google组上问这样一个问题。那是拉比特人常去的地方

    更新3


    作为代理上此类事件的解决方案,可以使用。特定的
    user.deleted
    user.password.changed
    事件由代理发出

    经过大量的实验和调试,我采用了

    所以现在,我不再试图跟踪Spring和Rabbit代码中的
    ShutdowsSignalException
    ListenerContainerConsumerFailedEvent
    事件,而是在
    SimpleMessageListenerContainer
    RabbitTemplate
    之间进行跟踪
    spring.rabbitmq.publisher-confirms=true
    spring.rabbitmq.template.mandatory=true
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.annotation.*;
    import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
    import org.springframework.cloud.endpoint.RefreshEndpoint;
    import org.springframework.messaging.MessageHeaders;
    import org.springframework.stereotype.Component;
    
    import static org.springframework.amqp.core.ExchangeTypes.TOPIC;
    
    @Component
    public class ReuathenticationListener {
    
        private static Logger log = LoggerFactory.getLogger(ReuathenticationListener.class);
    
        @Autowired private RabbitProperties rabbitProperties;
        @Autowired private RefreshEndpoint refreshEndpoint;
        @Autowired private CachingConnectionFactory connectionFactory;
    
        @RabbitListener(
            id = "credential_expiry_listener",
            bindings = @QueueBinding(value = @Queue(value="credentials.expiry", autoDelete="true", durable="false"),
                exchange = @Exchange(value="amq.rabbitmq.event", type=TOPIC, internal="true", durable="true"),
                key = "user.#")
        )
        public void expiryHandler(final MessageHeaders headers) {
            final String key = (String) headers.get("amqp_receivedRoutingKey");
            // See: https://www.rabbitmq.com/event-exchange.html
            if (!key.equals("user.deleted") &&
                !key.equals("user.authentication.failure")) {
                return;
            }
    
            final String failedName = (String) headers.get("name");
            final String prevUsername = rabbitProperties.getUsername();
    
            if (!failedName.equals(prevUsername)) {
                log.debug("Ignore expiry of unrelated user: " + failedName);
                return;
            }
    
            log.info("Refreshing Rabbit credentials...");
            refreshEndpoint.refresh();
            log.info("Refreshed username: '" + prevUsername + "' => '" + rabbitProperties.getUsername() + "'");
    
            connectionFactory.setUsername(rabbitProperties.getUsername());
            connectionFactory.setPassword(rabbitProperties.getPassword());
            connectionFactory.resetConnection();
    
            log.info("CachingConnectionFactory reset, reconnection should now begin.");
        }
    }