java使用sshd 實現sftp 自定義顯示目錄

☆♂安♀★發表於2024-07-03

pom.xml

 <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.28</version>
        </dependency>
        <dependency>
            <groupId>org.apache.sshd</groupId>
            <artifactId>sshd-core</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.sshd</groupId>
            <artifactId>sshd-sftp</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.7.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.7.11</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.50</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

    </dependencies>

PasswordAuthenticator

package com.zhianchen.sftptest.auth;

import cn.hutool.core.collection.CollectionUtil;
//import com.upi.gftp.common.util.EncryMachineutils;
//import com.upi.gftp.domain.dto .GftpuserDTO;
//import com.upi.gftp.domain.enums.AuthTypeEnum;
//import com.upigftp .domain.enums .StatusEnum;
//import com.upi.gftp.domain .enums.UserTypeEnum;
//import com.upi.gftpservice.SftpSeverService;
//import com.upi.gftp.service.UserService;
//import com.upi.gftp.service .config.EnckeyInfoConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.server .auth.AsyncAuthException;
import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
import org.apache.sshd.server .channel.ChannelSession;
import org.apache.sshd.server.command .Command;
import org.apache .sshd.server .session .ServerSession;
import org.apache .sshd.server .shell.ShellFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
/**
 * 密碼登入驗證
 */
@Slf4j
@Component
public class PasswordAuthenticator implements org.apache.sshd.server.auth.password.PasswordAuthenticator, ShellFactory {

    /**
     * 登入
     * @param s
     * @param s1
     * @param serverSession
     * @return
     * @throws PasswordChangeRequiredException
     * @throws AsyncAuthException
     */
    @Override
    public boolean authenticate(String s, String s1, ServerSession serverSession) throws PasswordChangeRequiredException, AsyncAuthException {
        return false;
    }

    @Override
    public Command createShell(ChannelSession channelSession) throws IOException {
        log.error(channelSession.getSession().getUsername()+" this sftp server only allows command;");
        throw  new UnsupportedOperationException(channelSession.getSession().getUsername()+" this sftp server only allows command;");
        //return null;
    }
}

PublicKeyAuthenticator

package com.zhianchen.sftptest.auth;

import cn.hutool.core.collection.CollectionUtil;
//import com.upi.gftp.common.constants.CommonConstants;
//import com.upi.gftp.common.util.secretkey.PublicKeyutils;
//import com.upi.gftp.domain.dto.GftpuserDT0;
//import com.upi.gftp .domain.enums .AuthTypeEnum;
//import com.upi.gftp.domain.enums.StatusEnum;
//import com.upi.gftp .domain .enums .UserTypeEnum;
//import com.upi.gftp.service.SftpSeverService;
//import com.upi.gftp.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.server.auth.AsyncAuthException;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache .sshd.server .channel.ChannelSession;
import org.apache .sshd .server . command .Command;
import org.apache .sshd.server .session.ServerSession;
import org.apache .sshd.server .shell.ShellFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.security.PublicKey;
import java.util.List;

/**
 * 秘鑰登入
 */
@Slf4j
@Component
public class PublicKeyAuthenticator implements PublickeyAuthenticator, ShellFactory {
    @Override
    public boolean authenticate(String s, PublicKey publicKey, ServerSession serverSession) throws AsyncAuthException {
        String algorithm = publicKey.getAlgorithm();

        return false;
    }

    @Override
    public Command createShell(ChannelSession channelSession) throws IOException {
        log.error(channelSession.getSession().getUsername()+" this sftp server only allows command;");
        throw  new UnsupportedOperationException(channelSession.getSession().getUsername()+" this sftp server only allows command;");
    }
}

SftpEventListener

package com.zhianchen.sftptest.startup;

import com.alibaba.fastjson.JSON;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.*;
import cn.hutool.core.util.*;
//import com.upi.gftp.core .Filepipeline;
//import com.upi.gftp.core.domain.FilePipelineContext;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.sftp.common.SftpException;
import org.apache.sshd.sftp.server .FileHandle;
import org.apache.sshd.sftp.server .Handle;
import org.springframework.stereotype .Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 */
@Slf4j
@Component
public class SftpEventListener implements org.apache.sshd.sftp.server.SftpEventListener {
    @Override
    public void initialized(ServerSession session, int version) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.initialized(session, version);
        log.info("initialized ... user {},version {}",session.getUsername(),version);
    }

    @Override
    public void creating(ServerSession session, Path path, Map<String, ?> attrs) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.creating(session, path, attrs);
        log.info("creating ... user {},version {}",session.getUsername(),path);
        int name=path.getNameCount();
    }

    @Override
    public void opening(ServerSession session, String remoteHandle, Handle localHandle) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.opening(session, remoteHandle, localHandle);
        log.info("opening ... user {} start",session.getUsername());
        String  fileHandle=  localHandle.getFileHandle();
        if(fileHandle==null){
            return;
        }
        //檔案上傳控制代碼
        if(localHandle instanceof FileHandle){
            Set<StandardOpenOption> openOptions = ((FileHandle) localHandle).getOpenOptions();
            log.info("opening ... user {},options {}",session.getUsername(),JSON.toJSONString(openOptions));
            if(openOptions.contains(StandardOpenOption.WRITE)){
                String s = localHandle.getFile().getFileName().toString();
            }else if(openOptions.contains(StandardOpenOption.READ)){
                String s = localHandle.getFile().getFileName().toString();
            }
        }
        log.info("opening ... user {} finish",session.getUsername());
    }

    @Override
    public void received(ServerSession session, int type, int id) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.received(session, type, id);
        log.info("received ... user {} type {} id {}",session.getUsername(),type,id);
        if(type==18){
            //不允許修改目錄和檔名稱
            throw new SftpException(600,"modify file or directory name is not allowed!");
        }
    }

    @Override
    public void created(ServerSession session, Path path, Map<String, ?> attrs, Throwable thrown) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.created(session, path, attrs, thrown);
        log.info("created ... user {} ",session.getUsername());
    }

    @Override
    public void moved(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts, Throwable thrown) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.moved(session, srcPath, dstPath, opts, thrown);
    }

    @Override
    public void moving(ServerSession session, Path srcPath, Path dstPath, Collection<CopyOption> opts) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.moving(session, srcPath, dstPath, opts);
    }

    @Override
    public void read(ServerSession session, String remoteHandle, FileHandle localHandle, long offset, byte[] data, int dataOffset, int dataLen, int readLen, Throwable thrown) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.read(session, remoteHandle, localHandle, offset, data, dataOffset, dataLen, readLen, thrown);
        log.info("read ... user {},file {} ",session.getUsername(),localHandle.getFile().toString());
    }

    @Override
    public void closed(ServerSession session, String remoteHandle, Handle localHandle, Throwable thrown) throws IOException {
        //org.apache.sshd.sftp.server.SftpEventListener.super.closed(session, remoteHandle, localHandle, thrown);
        log.info("closed ... user {} start",session.getUsername());
        //檔案上傳控制代碼
        if(localHandle instanceof FileHandle){
            Set<StandardOpenOption> openOptions = ((FileHandle) localHandle).getOpenOptions();
            log.info("opening ... user {},options {}",session.getUsername(),JSON.toJSONString(openOptions));
            if(openOptions.contains(StandardOpenOption.WRITE)&&!localHandle.isOpen()){
                //FilleSftp
                String s = localHandle.getFile().getFileName().toString();
            }else if(openOptions.contains(StandardOpenOption.READ)){
                String s = localHandle.getFile().getFileName().toString();
            }
        }
        localHandle.close();
        log.info("closed ... user {} finish",session.getUsername());
    }
}

SftpServerConfig

package com.zhianchen.sftptest.startup;

import com.zhianchen.sftptest.auth.PasswordAuthenticator;
import com.zhianchen.sftptest.auth.PublicKeyAuthenticator;
import com.zhianchen.sftptest.config.CommonConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache .sshd.common .NamedFactory;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.apache .sshd.server .ServerBuilder;
import org .apache .sshd .server .SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j

public class SftpServerConfig {
    @Autowired
    private CommonConfig commonConfig;

    @Autowired
    private PasswordAuthenticator passwordAuthenticator;

    @Autowired
    private PublicKeyAuthenticator publicKeyAuthenticator;

    @Autowired
    private SftpEventListener sftpEventListener;

    @Bean
    public void sftpServer() {
//建立SshServer物件
        log.info("Create ssh server instance...");
        SshServer sshd = SshServer.setUpDefaultServer();
        log.info("set ssh server port...port:[}.", commonConfig.getPort());
        sshd.setPort(commonConfig.getPort());
        //設定預設的備名檔案,如過檔案不存在會建立
        log.info("Set key pair provider...");
        sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider());
        //設定密銷進行登入驗證
        log.info("Set public key authenticator...");
        sshd.setPublickeyAuthenticator(publicKeyAuthenticator);//設定使用者名稱和密碼進行登入驗證
        log.info("Set password authenticator...");
        sshd.setPasswordAuthenticator(passwordAuthenticator);
//設定sftp子系統
        log.info("Set sftp sub system...");
        SftpSubsystemFactory sftpSubsystemFactory = new SftpSubsystemFactory();
        log.info("Add sftp event listener...");
        sftpSubsystemFactory.addSftpEventListener(sftpEventListener);
        sshd.setSubsystemFactories(Collections.singletonList(sftpSubsystemFactory));

        log.info("Set up home folder for users...");
        Path frontendFolder = Paths.get("/sftpfroent"); //gftpPathConfig.getFrontendsftp()
        if (Files.notExists(frontendFolder) || Files.isRegularFile(frontendFolder)) {
            log.info("GFTP frontend folder not exists, will create folder. folder:[}.", frontendFolder);
            try {
                Files.createDirectories(frontendFolder);
            } catch (IOException e) {
                log.error("sftp server start failed! create frontend folder failed! folder:1.", frontendFolder, e);
                return;
            }
        }


        sshd.setFileSystemFactory(new VirtualFileSystemFactory(frontendFolder.toAbsolutePath()) {

            @Override
            public Path getUserHomeDir(SessionContext session) throws IOException {
                String username = session.getUsername();


                if (StringUtils.isBlank(username)) {
                    log.info("client username blank!");
                    throw new RuntimeException("client username blank!");
                }
                Path homeDir = getUserHomeDir(username);
                if (homeDir == null) {
                    //這裡給每個使用者修改為預設目錄+使用者名稱
                    homeDir = getDefaultHomeDir().resolve(username);
                    createHomeDir(username, homeDir);
                } else {
                    createHomeDir(username, homeDir);
                }
                return homeDir;

            }

            public void createHomeDir(String username, Path homeDir) throws RemoteException {
                if (Files.notExists(homeDir) || Files.isRegularFile(homeDir)) {
                    try {
                        Files.createDirectories(homeDir);
                    } catch (Exception e) {
                        log.error("can not create folder {}.", homeDir);
                        throw new RemoteException("Can not create folder " + homeDir);

                    }
                }
                setUserHomeDir(username, homeDir);
            }
        });
            //啟動ssh服務
        try {
            log.info("starting sftp server...");
            sshd.start();
            log.info("sftp server started normally,port:1}.", commonConfig.getPort());
        } catch (IOException e) {
            log.error("sftp server start failed!", e);
        }
    }

    private List<KeyExchangeFactory> getKeyExchangeFactories() {
        List<KeyExchangeFactory> keyExchangeFactories = new ArrayList<>();

// Add the Diffie-Hellman-group1-sha1 key exchange factory
        keyExchangeFactories.addAll(NamedFactory.setUpTransformedFactories(false, Arrays.asList(BuiltinDHFactories.dhg1, BuiltinDHFactories.dhg14),
                ServerBuilder.DH2KEX));

        keyExchangeFactories.addAll(BuiltinDHFactories.VALUES.stream().map(ServerBuilder.DH2KEX).collect(Collectors.toList()));
        return keyExchangeFactories;


    }
}

CommonConfig

package com.zhianchen.sftptest.config;
/*
* @author : zrht_chenzhian
* @date :2023/12/6 16:07
* @description
* @moduified By:
*/

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "gftp")
public class CommonConfig {

    //tcp埠
    private Integer tcpPort;
    /**
     * gftp系統ip
     */
    private String host;
    /**
     * gftp埠
     */
    private Integer port;
}

相關文章