迁移到 Spring Boot 3 - 如何处理来自服务器的“[email protected]”请求?

问题描述 投票:0回答:2

我们的团队有一个应用程序,每天使用 Spring Integration SFTP 从远程服务器获取和处理一次文件。现在,我正在从 Spring Boot 2.7 迁移到 Spring Boot 3。

迁移到 Spring Boot 3 后,我注意到每分钟都会记录以下内容(在 WARN 级别):

2023-08-15T15:00:48.427+02:00  WARN 21256 --- []-nio2-thread-3] i.DefaultSftpClient$SftpChannelSubsystem : handleUnknownChannelRequest(SftpChannelSubsystem[id=0, recipient=0]-ClientSessionImpl[<username>@<host>/<ip>:<port>][sftp]) Unknown channel request: [email protected][want-reply=true]
2023-08-15T15:01:48.430+02:00  WARN 21256 --- [-nio2-thread-13] i.DefaultSftpClient$SftpChannelSubsystem : handleUnknownChannelRequest(SftpChannelSubsystem[id=0, recipient=0]-ClientSessionImpl[<username>@<host>/<ip>:<port>][sftp]) Unknown channel request: [email protected][want-reply=true]

我假设这是从服务器发送到客户端的Keepalive请求。为什么我们的应用程序无法处理该请求?迁移到 Spring Boot 3 之前未显示警告消息。

即使客户端无法处理来自服务器的 keepalive 请求,它仍然会按其应有的方式获取文件。问题是日志文件将充满所有这些不必要的警告消息,并且我们将无法看到其他重要日志。

这是我们用于 SessionFactory 的 Spring Bean

@Bean
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
    final DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
    factory.setHost(sftpHost);
    factory.setPort(sftpPort);
    factory.setUser(sftpUser);

    if (sftpPrivateKey != null) {
        factory.setPrivateKey(sftpPrivateKey);
        factory.setPrivateKeyPassphrase(sftpPrivateKeyPassphrase);
    } else {
        factory.setPassword(sftpPassword);
    }

    factory.setAllowUnknownKeys(true);

    return new CachingSessionFactory<>(factory);
}
spring spring-boot spring-integration spring-integration-sftp spring-boot-3
2个回答
0
投票

看起来您正面临这种情况:https://issues.apache.org/jira/browse/SSHD-1237.

根据他们的建议,可以注入外部配置的

SshClient

SshClient client = SshClient.setupDefaultClient();
List<RequestHandler<ConnectionService>> oldHandlers = client.getGlobalRequestHandlers();
List<RequestHandler<ConnectionService>> handlers = new ArrayList<>();
if (GenericUtils.isNotEmpty(oldHandlers)) {
   handlers.add(oldHandlers);
}
handlers.add(KeepAliveHandler.INSTANCE);
client.setGlobalRequestHandlers(handlers);

然后使用这个ctor:

/**
 * Instantiate based on the provided {@link SshClient}, e.g. some extension for HTTP/SOCKS.
 * @param sshClient the {@link SshClient} instance.
 * @param isSharedSession true if the session is to be shared.
 */
public DefaultSftpSessionFactory(SshClient sshClient, boolean isSharedSession) {

0
投票

遇到了同样的问题,并通过创建一个忽略 Keep Alive 请求的自定义 SFTP 会话工厂来解决它(受到 Artem Bilan 指向

DefaultSftpSessionFactory
类的答案的启发)。

这是自定义 SFTP 会话工厂的示例:

import org.apache.sshd.client.ClientBuilder;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.auth.keyboard.UserInteraction;
import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
import org.apache.sshd.client.global.OpenSshHostKeysHandler;
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
import org.apache.sshd.client.keyverifier.RejectAllServerKeyVerifier;
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
import org.apache.sshd.common.channel.RequestHandler;
import org.apache.sshd.common.session.ConnectionService;
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
import org.springframework.integration.sftp.session.ResourceKnownHostsServerKeyVerifier;

import java.util.List;

import static java.util.Arrays.asList;

public class CustomSftpSessionFactory extends DefaultSftpSessionFactory {
    private static final List<RequestHandler<ConnectionService>> GLOBAL_REQUEST_HANDLERS = asList(
        OpenSshHostKeysHandler.INSTANCE,
        CustomOpenSshKeepAliveHandler.INSTANCE
    );

    public CustomSftpSessionFactory(String password) {
        super(setUpDefaultClient(password), false);
    }

    private static SshClient setUpDefaultClient(String password) {
        // Taken from the SshClient.setUpDefaultClient()
        final ClientBuilder clientBuilder = ClientBuilder.builder();
        clientBuilder.globalRequestHandlers(GLOBAL_REQUEST_HANDLERS);

        // Taken from DefaultSftpSessionFactory.doInitInnerClient()
        // It would be analogous to the factory.setAllowUnknownKeys()
        clientBuilder.serverKeyVerifier(AcceptAllServerKeyVerifier.INSTANCE);

        final SshClient sshClient = clientBuilder.build();

        // This is analogous to the factory.setPassword()
        sshClient.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(password));

        sshClient.setUserInteraction(UserInteraction.NONE);

        // You may configure other SSH Client properties, like Private Keys.

        return sshClient;
    }
}

注意

GLOBAL_REQUEST_HANDLERS
包含 2 个处理程序:

  • 首先是使用
    OpenSshHostKeysHandler
    时通常提供的默认
    SshClient.setUpDefaultClient()
    (请参阅
    DEFAULT_GLOBAL_REQUEST_HANDLERS
    中的
    org.apache.sshd.client.ClientBuilder
    )。
  • 然后是第二个,这是自定义的一个,它允许我们处理(并忽略)那些保持活动的请求。

第二个的实现是这样的:

import org.apache.sshd.common.channel.RequestHandler;
import org.apache.sshd.common.global.AbstractOpenSshHostKeysHandler;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.keys.BufferPublicKeyParser;

import java.security.PublicKey;
import java.util.Collection;

public class CustomOpenSshKeepAliveHandler extends AbstractOpenSshHostKeysHandler {
    public static final CustomOpenSshKeepAliveHandler INSTANCE = new CustomOpenSshKeepAliveHandler();

    private static final String REQUEST = "[email protected]";

    public CustomOpenSshKeepAliveHandler() {
        super(REQUEST);
    }

    public CustomOpenSshKeepAliveHandler(BufferPublicKeyParser<? extends PublicKey> parser) {
        super(REQUEST, parser);
    }

    protected RequestHandler.Result handleHostKeys(Session session, Collection<? extends PublicKey> keys, boolean wantReply, Buffer buffer) {
        // You may want to consider doing something if wantReply is true.
        // For my own purposes, answering with Replied was good enough.
        return Result.Replied;
    }
}

最后,剩下要做的就是使用它。像这样,例如:

final DefaultSftpSessionFactory factory = new CustomSftpSessionFactory(password);
factory.setHost(host);
factory.setPort(port);
factory.setUser(username);
© www.soinside.com 2019 - 2024. All rights reserved.