Apache MINA SSHD SFTP服务器:实现特定路径防覆盖与只读目录配置
解决方案:在文件打开阶段拦截写入操作
你的问题核心在于writing方法触发时机太晚——此时文件已经被创建或截断。正确的做法是在打开文件用于写入的阶段就拦截,也就是利用SftpEventListener的open方法,它能获取文件打开模式、路径和会话信息,完全满足你的需求。
实现步骤
1. 自定义SftpEventListener,覆写open方法
在open方法中,我们可以提前判断用户操作类型(写入/覆盖)、目标路径和用户名,直接抛出权限异常阻止后续操作,避免产生0字节文件或截断原有文件。
import org.apache.sshd.sftp.client.SftpClient; import org.apache.sshd.sftp.common.SftpConstants; import org.apache.sshd.sftp.common.SftpException; import org.apache.sshd.sftp.common.SftpFile; import org.apache.sshd.sftp.common.SftpOpenFileRequest; import org.apache.sshd.sftp.common.SftpSession; import org.apache.sshd.sftp.server.SftpEventListenerAdapter; import java.io.IOException; import java.util.Collection; import java.util.Set; public class RestrictiveSftpEventListener extends SftpEventListenerAdapter { // 可根据实际需求替换为配置注入的参数 private static final String RESTRICTED_USER = "restricted_user"; private static final Set<String> READ_ONLY_DIRECTORIES = Set.of("/readonly_dir", "/data/protected"); private static final Set<String> NO_OVERWRITE_FILES = Set.of("/critical/config.conf", "/docs/important.txt"); @Override public void open(SftpSession session, String remoteHandle, SftpOpenFileRequest request) throws IOException { String username = session.getUsername(); String targetPath = request.getFile(); Collection<SftpClient.OpenMode> openModes = request.getOpenModes(); // 标准化路径,避免相对路径或重复斜杠的问题 String normalizedPath = session.getFileSystem().normalize(targetPath).toString(); // 判断是否为写入类操作(创建、覆盖、写入) boolean isWriteOperation = openModes.contains(SftpClient.OpenMode.WRITE) || openModes.contains(SftpClient.OpenMode.TRUNCATE) || openModes.contains(SftpClient.OpenMode.CREATE); // 拦截只读目录下的写入操作 boolean isInReadOnlyDir = READ_ONLY_DIRECTORIES.stream() .anyMatch(dir -> normalizedPath.startsWith(dir)); if (isInReadOnlyDir && isWriteOperation) { throw new SftpException(SftpConstants.SSH_FX_PERMISSION_DENIED, "Directory is read-only: " + normalizedPath); } // 拦截特定用户覆盖禁止的文件 if (RESTRICTED_USER.equals(username)) { SftpFile targetFile = session.getFileSystem().getFile(normalizedPath); if (targetFile.exists() && NO_OVERWRITE_FILES.contains(normalizedPath) && isWriteOperation) { throw new SftpException(SftpConstants.SSH_FX_PERMISSION_DENIED, "Overwriting forbidden: " + normalizedPath); } } super.open(session, remoteHandle, request); } }
2. 将自定义EventListener注册到SFTP子系统
在Spring Boot中配置SFTP服务器时,把自定义的监听器添加到SftpSubsystemFactory:
import org.apache.sshd.server.SshServer; import org.apache.sshd.sftp.server.SftpSubsystemFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Set; @Configuration public class SftpServerConfig { @Bean public SshServer sshServer() { SshServer sshServer = SshServer.setUpDefaultServer(); // 其他配置:端口、认证方式等... // 配置SFTP子系统并添加自定义监听器 SftpSubsystemFactory sftpSubsystemFactory = new SftpSubsystemFactory(); sftpSubsystemFactory.addSftpEventListener(new RestrictiveSftpEventListener()); sshServer.setSubsystemFactories(Set.of(sftpSubsystemFactory)); return sshServer; } }
关键说明
- 时机优势:
open方法在文件实际创建/截断前触发,抛出异常会直接终止操作,不会留下无效文件或损坏原有文件。 - 错误码规范:使用
SftpConstants.SSH_FX_PERMISSION_DENIED(错误码3),客户端会收到标准的权限拒绝提示,体验更友好。 - 路径标准化:通过
session.getFileSystem().normalize()处理路径,避免相对路径(如../)绕过权限检查的问题。
如果需要更复杂的权限控制(比如基于用户组的规则),可以扩展SftpFileSystemView或FileHandleFactory,但针对你的需求,上述方案已经足够简洁有效。
内容的提问来源于stack exchange,提问作者wileecoyote
相关产品推荐
相关产品推荐

