当前位置: 首页 > news >正文

基于Apache MINA SSHD配置及应用

Apache MINA SSHD 是一个基于 Java 的 SSH 服务器和客户端实现,它是 Apache MINA 项目的一部分,提供了完整的 SSH 协议支持。

主要特性

  1. SSH 协议支持

    • 支持 SSH2 协议

    • 兼容大多数 SSH 客户端

    • 支持多种加密算法和密钥交换方法

  2. 服务器功能

    • 可嵌入的 SSH 服务器

    • 支持密码认证和公钥认证

    • 支持端口转发

    • 可自定义的 shell 和命令执行

  3. 客户端功能

    • 完整的 SSH 客户端实现

    • 支持交互式和非交互式会话

    • 支持 SCP 和 SFTP 文件传输

  4. 具体配置

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClientFactory;
import org.apache.sshd.sftp.common.SftpConstants;
import org.springframework.lang.NonNull;
import java.io.ByteArrayOutputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;/*** SshClient服务类** @author gengzhy* @since 2025/5/29 16:57*/
@Slf4j
public class SshClientService {private final SshClient sshClient;private final SftpProperties properties;public SshClientService(SshClient sshClient, SftpProperties properties) {this.sshClient = sshClient;this.properties = properties;}/*** 读取文件** @param filePath - 文件路径* @return - Bytes*/public byte[] readOnce(@NonNull String filePath) throws IOException {return executeInternal(session -> {try (SftpClient sftpClient = sftpClient(session)) {SftpClient.Attributes stat = beforeReadCheck(sftpClient, filePath);try (InputStream in = new BufferedInputStream(sftpClient.read(filePath))) {ByteArrayOutputStream out = new ByteArrayOutputStream((int) stat.getSize());byte[] buffer = new byte[8192];int read;while ((read = in.read(buffer, 0, buffer.length)) != -1) {out.write(buffer, 0, read);}return out.toByteArray();}} catch (IOException e) {Throwable ex = getRootCause(e);log.error("sftp 读取文件异常: {}", ex.getMessage(), ex);throw new RuntimeException(ex.getMessage(), ex);}});}/*** 读取文件** @param filePath - 文件路径* @return - Bytes*/public List<String> readLines(@NonNull String filePath) throws IOException {return readLines(filePath, null);}/*** 读取文件** @param filePath     - 文件路径* @param lineConsumer - 读取的数据行消费者* @return - Bytes*/public List<String> readLines(@NonNull String filePath, Consumer<String> lineConsumer) throws IOException {return executeInternal(session -> {try (SftpClient sftpClient = sftpClient(session)) {beforeReadCheck(sftpClient, filePath);try (BufferedReader br = new BufferedReader(new InputStreamReader(sftpClient.read(filePath), StandardCharsets.UTF_8))) {List<String> data = new ArrayList<>();String line;while ((line = br.readLine()) != null) {data.add(line);if (lineConsumer != null) {lineConsumer.accept(line);}}return data;}} catch (IOException e) {Throwable ex = getRootCause(e);log.error("sftp 读取文件异常: {}", ex.getMessage(), ex);throw new RuntimeException(ex.getMessage(), ex);}});}/*** 创建文件** @param filePath - 文件路径*/public void writeOnce(@NonNull String filePath, byte[] data) throws IOException {executeInternal(session -> {try (SftpClient sftpClient = sftpClient(session)) {mkDirsIfNotExists(sftpClient, getFileParentPath(filePath));try (SftpClient.CloseableHandle handle = sftpClient.open(filePath, SftpClient.OpenMode.Create, SftpClient.OpenMode.Write, SftpClient.OpenMode.Read)) {sftpClient.write(handle, 0L, data);}return null;} catch (IOException e) {Throwable ex = getRootCause(e);log.error("sftp 创建文件异常: {}", ex.getMessage(), ex);throw new RuntimeException(ex.getMessage(), ex);}});}/*** 递归创建目录(等效于 mkdir -p)** @param sftpClient SFTP 客户端* @param dirPath    目标路径(如 "/a/b/c")*/public void mkDirsIfNotExists(SftpClient sftpClient, String dirPath) throws IOException {if (dirPath == null) {return;}String[] parts = dirPath.split("/");StringBuilder currentPath = new StringBuilder();for (String part : parts) {if (part.isEmpty()) {continue;}currentPath.append("/").append(part);String path = currentPath.toString();if (stat(sftpClient, path) == null) {sftpClient.mkdir(path);}}}/*** 提供一个便于扩展的内容部执行方法,主要是基于{@link ClientSession}对象的一些列操作* <p>* 如:* 创建sftp客户端对象:{@link SftpClientFactory#instance()#createSftpClient(ClientSession)}* 创建shell执行命令对象:{@link ClientSession#createExecChannel(String)}}** @param execCall - 基于{@link ClientSession}对象的回调* @param <T>      - 返回数据类型* @return - obj*/public <T> T executeInternal(Function<ClientSession, T> execCall) throws IOException {try (ClientSession session = session()) {return execCall != null ? execCall.apply(session) : null;} catch (IOException e) {log.error("创建ssh会话异常·: {}", e.getMessage(), e);throw new IOException("创建ssh会话异常: " + e.getMessage(), e);}}private SftpClient.Attributes beforeReadCheck(SftpClient sftpClient, String filePath) {SftpClient.Attributes stat = stat(sftpClient, filePath);if (stat == null) {throw new RuntimeException("文件不存在【{" + filePath + "}】");}Asserts.isTrue(stat.isRegularFile(), "不支持的文件类型:" + FileType.getByActualType(stat.getType()));Asserts.isTrue(stat.getSize() <= Integer.MAX_VALUE, "文件过大");return stat;}private String getFileParentPath(String filePath) {String path = filePath.replaceAll("[\\\\/]+", "/");int index = path.lastIndexOf('/');return index == -1 ? null : path.substring(0, index);}/*** 获取文件信息,不存在返回null** @param sftpClient - sftp客户端* @param filePath   - 文件路径*/private SftpClient.Attributes stat(SftpClient sftpClient, String filePath) {try {return sftpClient.stat(filePath);} catch (IOException e) {return null;}}private SftpClient sftpClient(ClientSession session) throws IOException {return SftpClientFactory.instance().createSftpClient(session);}private ClientSession session() throws IOException {synchronized (sshClient) {if (!sshClient.isStarted()) {sshClient.start();}}ClientSession session = sshClient.connect(properties.getUsername(), properties.getHost(), properties.getPort()).verify(properties.getConnectTimeout()).getSession();session.addPasswordIdentity(properties.getPassword());session.auth().verify(properties.getAuthTimeout());return session;}private Throwable getRootCause(Throwable ex) {while (ex.getCause() != null) {ex = ex.getCause();}return ex;}@Getter@AllArgsConstructorprivate enum FileType {file(SftpConstants.SSH_FILEXFER_TYPE_REGULAR),dir(SftpConstants.SSH_FILEXFER_TYPE_DIRECTORY),symlink(SftpConstants.SSH_FILEXFER_TYPE_SYMLINK),special_file(SftpConstants.SSH_FILEXFER_TYPE_SPECIAL),SOCKET(SftpConstants.SSH_FILEXFER_TYPE_SOCKET),unknown(SftpConstants.SSH_FILEXFER_TYPE_UNKNOWN),;private final int actualType;public static FileType getByActualType(int actualType) {return Arrays.stream(FileType.values()).filter(item -> item.actualType == actualType).findFirst().orElseThrow(() -> new RuntimeException("Unknown file type: " + actualType));}}
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.time.Duration;@Getter
@Setter
@ConfigurationProperties(prefix = "sftp")
public class SftpProperties {/*** 主机IP*/private String host = "127.0.0.1";/*** 主机端口,默认22*/private int port = 22;/*** 用户名*/private String username = "";/*** 登录密码*/private String password = "";/*** 连接超时(ms),默认5000ms*/private Duration connectTimeout = Duration.ofMillis(5000);/*** 认证超时(ms),默认10000ms*/private Duration authTimeout = Duration.ofMillis(10000);
}
import org.apache.sshd.client.SshClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@ConditionalOnClass(SshClient.class)
@Configuration
@EnableConfigurationProperties(SftpProperties.class)
public class SshClientConfiguration {@BeanSshClientService sshClientService(SftpProperties properties) {return new SshClientService(SshClient.setUpDefaultClient(), properties);}
}
sftp:host: 127.0.0.1port: 22username: demopassword: pwdconnect-timeout: 5sauth-timeout: 5s

http://www.lryc.cn/news/580139.html

相关文章:

  • CppCon 2018 学习:OOP is dead, long live Data-oriented design
  • ABP VNext + RediSearch:微服务级全文检索
  • PyCharm 安装使用教程
  • Rust异步爬虫实现与优化
  • 全星 QMS:制造业全面质量管理的数字化全能平台
  • 鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
  • Jenkins-Publish HTML reports插件
  • 接口测试之postman
  • ZigBee通信技术全解析:从协议栈到底层实现,全方位解读物联网核心无线技术
  • 区块链技术核心组件及应用架构的全面解析
  • 7.4_面试_JAVA_
  • 【PyTorch】PyTorch预训练模型缓存位置迁移,也可拓展应用于其他文件的迁移
  • 基于PHP+MySQL实现(Web)英语学习与测试平台
  • 408第三季part2 - 计算机网络 - 计算机网络基本概念
  • 金融平衡术:创新与合规的突围之路
  • Spark从入门到实战:安装与使用全攻略
  • 使用 DigitalPlat 免费搭配 Cloudflare Tunnel 实现飞牛系统、服务及 SSH 内网穿透教程
  • Java SE--方法的使用
  • Kotlin中优雅的一行行读取文本文件
  • 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
  • 【笔记】PyCharm 2025.2 EAP 创建 Poetry 和 Hatch 环境的踩坑实录与反馈
  • 三体融合实战:Django+讯飞星火+Colossal-AI的企业级AI系统架构
  • Android WebView 性能优化指南
  • 《Java修仙传:从凡胎到码帝》第三章:缩进之劫与函数峰试炼
  • React Ref使用
  • React中的useState 和useEffect
  • 指环王英文版魔戒再现 Part 1 Chapter 01
  • 力扣 hot100 Day34
  • [Linux]内核态与用户态详解
  • java web5(黑马)