You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 04:22:30