当前位置: 首页 > 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.dtcms.com/a/266907.html

相关文章:

  • Python爬虫 模拟登录状态 requests版
  • 如何查看自己电脑的CUDA版本?
  • D3 面试题100道之(21-40)
  • 通过MaaS平台免费使用大模型API
  • Java 入门
  • 鸿蒙中判断两个对象是否相等
  • react案例动态表单(受控组件)
  • React 渲染深度解密:从 JSX 到 DOM 的初次与重渲染全流程
  • 深入解析XFS文件系统:原理、工具与数据恢复实战
  • 【Go语言-Day 13】切片操作终极指南:append、copy与内存陷阱解析
  • 替代MT6701,3D 霍尔磁性角度传感器芯片
  • Go语言的协程池Ants
  • yolo性能评价指标(训练后生成文件解读)results、mAP、Precision、Recall、FPS、Confienc--笔记
  • 韩顺平之第九章综合练习-----------房屋出租管理系统
  • 从0写自己的操作系统(3)x86操作系统的中断和异常处理
  • 02每日简报20250704
  • Spring Boot + 本地部署大模型实现:安全性与可靠性保障
  • 高档宠物食品对宠物的健康益处有哪些?
  • MySQL/MariaDB数据库主从复制之基于二进制日志的方式
  • 如何查看自己电脑的显卡信息?
  • 力扣hot100题(1)
  • C++26 下一代C++标准
  • 通用人工智能三大方向系统梳理
  • 学习者的Python项目灵感
  • 【python实用小脚本-128】基于 Python 的 Hacker News 爬虫工具:自动化抓取新闻数据
  • [数据结构]详解红黑树
  • 小架构step系列04:springboot提供的依赖
  • mobaxterm终端sqlplus乱码问题解决
  • 使用循环抵消算法求解最小费用流问题
  • opencv的颜色通道问题 rgb bgr