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; }