SFTP第一篇——搭建SFTP Server

一、Mac搭建Sftp Server

1、檢查ftp工具

打開命令行,輸入“ftp --help”,檢查是否已經安裝ftp工具。如果沒有,通過以下命令安裝:

  • brew install telnet

  • brew install inetutils

  • brew link --overwrite inetutils

如果沒有brew,通過以下命令安裝:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

2、運行ftp server

  • sudo -s launchctl load -w /System/Library/LaunchDaemons/ftp.plist
  • sudo -s launchctl unload -w /System/Library/LaunchDaemons/ftp.plist
  • ftp localhost

第一個命令,運行ftp server。同理,第二個是關閉ftp server。第三條命令是連接到該本地ftp server。

3、運行sftp server

在System preferences → shared → Remote login中選擇允許訪問,即可開啓sftp server。然後輸入sftp localhost即可連接到該sftp server。

二、搭建嵌入式Sftp Server

1、導入依賴

        <dependency>
            <groupId>org.apache.sshd</groupId>
            <artifactId>sshd-sftp</artifactId>
            <version>2.2.0</version>
        </dependency>

2、EmbeddedSftpServer

@Slf4j
public class EmbeddedSftpServer implements InitializingBean, SmartLifecycle {

    public static final int PORT = 0;

    private final SshServer server = SshServer.setUpDefaultServer();

    private volatile int port;

    private volatile boolean running;

    private DefaultSftpSessionFactory defaultSftpSessionFactory;

    public void setPort(int port) {
        this.port = port;
    }

    public void setDefaultSftpSessionFactory(DefaultSftpSessionFactory defaultSftpSessionFactory) {
        this.defaultSftpSessionFactory = defaultSftpSessionFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        final PublicKey allowedKey = decodePublicKey();
        this.server.setPublickeyAuthenticator((username, key, session) -> key.equals(allowedKey));
        this.server.setPort(this.port);
        this.server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostKey.ser").toPath()));
        server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
        final String pathname = System.getProperty("java.io.tmpdir") + File.separator + "sftpTest" + File.separator;
        log.info("create temp folder :: " + pathname);
        new File(pathname).mkdirs();
        server.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get(pathname)));
    }

    private PublicKey decodePublicKey() throws Exception {
        InputStream stream = new ClassPathResource("/keys/sftp_rsa.pub").getInputStream();
        byte[] keyBytes = StreamUtils.copyToByteArray(stream);
        // strip any newline chars
        while (keyBytes[keyBytes.length - 1] == 0x0a || keyBytes[keyBytes.length - 1] == 0x0d) {
            keyBytes = Arrays.copyOf(keyBytes, keyBytes.length - 1);
        }
        byte[] decodeBuffer = Base64Utils.decode(keyBytes);
        ByteBuffer bb = ByteBuffer.wrap(decodeBuffer);
        int len = bb.getInt();
        byte[] type = new byte[len];
        bb.get(type);
        if ("ssh-rsa".equals(new String(type, "UTF-8"))) {
            BigInteger e = decodeBigInt(bb);
            BigInteger m = decodeBigInt(bb);
            RSAPublicKeySpec spec = new RSAPublicKeySpec(m, e);
            return KeyFactory.getInstance("RSA").generatePublic(spec);

        } else {
            throw new IllegalArgumentException("Only supports RSA");
        }
    }

    private BigInteger decodeBigInt(ByteBuffer bb) {
        int len = bb.getInt();
        byte[] bytes = new byte[len];
        bb.get(bytes);
        return new BigInteger(bytes);
    }

    @Override
    public boolean isAutoStartup() {
        return PORT == this.port;
    }

    @Override
    public int getPhase() {
        return Integer.MAX_VALUE;
    }

    @Override
    public void start() {
        try {
            this.server.start();
            log.info("Embedded Sftp Server started");
            this.defaultSftpSessionFactory.setPort(this.server.getPort());
            this.running = true;
        } catch (IOException e) {
            log.error("Embedded Sftp Server starting failure", e);
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void stop(Runnable callback) {
        stop();
        callback.run();
    }

    @Override
    public void stop() {
        if (this.running) {
            try {
                server.stop(true);
                log.info("Embedded Sftp Server stopped");
            } catch (Exception e) {
                log.info("Embedded Sftp Server stop failure", e);
                throw new IllegalStateException(e);
            } finally {
                this.running = false;
            }
        }
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }
}

3、key文件

sftp_rsa(Private key)

-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAzEAf70wmOkBBfvz+92UGc4I+SwVBvICj2wF3VnBP19IN44LD
6cW+/jRoqjaT/qFSELC/UO0wbL9f1+8XOrHekpjP1Ez/CxdM5X9BkrxHoQtgU/fB
gog6iPASqWYxujvPqTxAWPDOYhji7Q1Es5Yc8le5D3AA2Cx5f89X7LFPsdlDC+wa
EpoIdFz3Nrvhr+vufElZNoqQGTpmlmQ8s1gHN7HM8/w5EHkf3KY9OOx1XNWW/zOW
uGo2CNfCmvplYSnokJx3NzSsBOmcXMJWnG1Sv33sqX6Xqx0DpY+hHun+oboZ1cfc
eNlddQONU6IffFiNhFQ3qruBHE8RxyZ7SYaW3QIDAQABAoIBAQCclFgm+eigZVwQ
fuDzRTZR3Lnmhywi1zdGEHStBkKfP/+3typ7j0Xg2MqYGmkQHhmsg+LWpk6mP3u5
LShQrcTj+1Pv++rVVNJ1aT4awE3lLrR1Co0FhWviSLD1vktG6s1CftcRl+GPoGZu
tepCBkVAn3FWXVW4YzftfEV6RV/EBVzZLySXeaQyKl8ln3yEBpOPkevy8uLC20O9
OKDGa30gEP215Kvx1rkvs6jXeMdeH+reEfvyYD8+SMcE60lyY8ntwKhrd/OiZP48
Fqp4kVEBf/u5DSeQbABLCW2uteHAMFzm/weOHDIIBm2aQzmrokw8IYuYiAyTpnDd
yqlJN+99AoGBAOegXlfK/6aVxpeygRatSRD+JI9p/7Hi9ShQ/VCANyc1RTKvDHIA
j5On1Hnwm1czFMr3CPs78MQa8uDlZEa19HbUAEgSp9X6nIPYi5Fo0Q3Fyp1O122S
QAA0mthGiEsneWMgRDordHgGFVoWcDbTWCH61bkisNufK90b/90TXVoPAoGBAOG+
SyW5aJTGHw01vHyNnnMWyloNMwd55vGp7Zhb7m1qUIci8WixJ3Jr1exLhhnc8Bpl
DU2sA4x2r6c1yozNozAw/KkLoa0JFISUg8eqprO0kMg6W9SP57xW2ooUMwEOJbkC
pjlgVcw3AXPfBZwCmujNG0wxc5TVVulgjQk0y1xTAoGBAK9RdjdTYp/vfAq0RPsq
HETtaDTZEX3OgKuMacA13AkkTAUp8+ySOhqUDMJjeODOvC1IQJcQ7pMwpqfNWVIg
RTJwEup6nGjdMPymujVMtfeLv2nEFFFOQn0lVBLhiCYCceGyuZGh9J0oVZ8DntoQ
rAPEPWLNPDpvxx6sI8Vs89rHAoGATLypQuuh92DZ0V3A8v4ZLLpEkxQFkrcHoILJ
N4+Yny0Srr1cHuCJrkWl9Ks/rK8EF5TeTtb4Zdk6oLaSYgbNQGaGnNhNX0rE5MSv
f0ItZM0uokHkUX+RoN5Nb76qD+PFQvz5kGuE/uR74+2eNIhWLGj8rIvq5F8ZKkAd
8VE3B+0CgYEAnrh436/L4s9RI9kbKfd99PEl87DFOYB3/v4g4n4Xoi9843dYDjgX
bl1JLbD2jv5HYMs55sHK9Rz/aWiTTDCoONkHL5b84ZDrPJnKzzzwMAND4RivJBYK
ORr+P2OrWEIt57CvLxTYB2RjHQdJ7+r8fxjyRGkkkxJScdsDhCBYisk=
-----END RSA PRIVATE KEY-----

sftp_rsa.pub(public key)

AAAAB3NzaC1yc2EAAAADAQABAAABAQDMQB/vTCY6QEF+/P73ZQZzgj5LBUG8gKPbAXdWcE/X0g3jgsPpxb7+NGiqNpP+oVIQsL9Q7TBsv1/X7xc6sd6SmM/UTP8LF0zlf0GSvEehC2BT98GCiDqI8BKpZjG6O8+pPEBY8M5iGOLtDUSzlhzyV7kPcADYLHl/z1fssU+x2UML7BoSmgh0XPc2u+Gv6+58SVk2ipAZOmaWZDyzWAc3sczz/DkQeR/cpj047HVc1Zb/M5a4ajYI18Ka+mVhKeiQnHc3NKwE6ZxcwlacbVK/feypfperHQOlj6Ee6f6huhnVx9x42V11A41Toh98WI2EVDequ4EcTxHHJntJhpbd

4、SftpTestUtils

public class SftpTestUtils {

    private static final String SFTP_TEMP_FOLDER = "sftpTempFolder";

    public static void createTestFiles(RemoteFileTemplate<LsEntry> template, final String... fileNames) {
        if (template != null) {
            final ByteArrayInputStream stream = new ByteArrayInputStream("TestData".getBytes());
            template.execute((SessionCallback<LsEntry, Void>) session -> {
                try {
                    session.mkdir(SFTP_TEMP_FOLDER);
                } catch (Exception e) {
                    assertThat(e.getMessage(), containsString("failed to create"));
                }
                for (int i = 0; i < fileNames.length; i++) {
                    stream.reset();
                    session.write(stream, SFTP_TEMP_FOLDER + "/" + fileNames[i]);
                }
                return null;
            });
        }
    }

    public static void cleanUp(RemoteFileTemplate<LsEntry> template, final String... fileNames) {
        if (template != null) {
            template.execute((SessionCallback<LsEntry, Void>) session -> {
                for (int i = 0; i < fileNames.length; i++) {
                    try {
                        // delete temp file
                        session.remove(SFTP_TEMP_FOLDER + "/" + fileNames[i]);
                    } catch (IOException e) {
                    }
                }
                // delete temp folder
                session.rmdir(SFTP_TEMP_FOLDER);
                return null;
            });
        }
    }

    public static boolean fileExists(RemoteFileTemplate<LsEntry> template, final String... fileNames) {
        if (template != null) {
            return template.execute(session -> {
                ChannelSftp channel = (ChannelSftp) session.getClientInstance();
                for (int i = 0; i < fileNames.length; i++) {
                    try {
                        SftpATTRS stat = channel.stat(SFTP_TEMP_FOLDER + "/" + fileNames[i]);
                        if (stat == null) {
                            System.out.println("stat returned null for " + fileNames[i]);
                            return false;
                        }
                    } catch (SftpException e) {
                        System.out.println("Remote file not present: " + e.getMessage() + ": " + fileNames[i]);
                        return false;
                    }
                }
                return true;
            });
        } else {
            return false;
        }
    }

}

該工具類用來創建一些臨時目錄和文件,供測試類調用。

5、SftpTestConfig配置類

@TestConfiguration
public class SftpTestConfig {
    @Bean
    public DefaultSftpSessionFactory defaultSftpSessionFactory() {
        DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory();
        factory.setPort(0);
        factory.setHost("localhost");
        factory.setUser("user");
        factory.setAllowUnknownKeys(true);
        factory.setPrivateKey(new ClassPathResource("/keys/sftp_rsa"));
        factory.setPrivateKeyPassphrase("password");
        return factory;
    }

    @Bean
    public CachingSessionFactory cachingSessionFactory() {
        return new CachingSessionFactory(defaultSftpSessionFactory());
    }

    @Bean
    public EmbeddedSftpServer embeddedSftpServer() {
        EmbeddedSftpServer sftpServer = new EmbeddedSftpServer();
        sftpServer.setPort(0);
        sftpServer.setDefaultSftpSessionFactory(defaultSftpSessionFactory());
        return sftpServer;
    }
}

6、測試類

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SftpTestConfig.class}, loader = AnnotationConfigContextLoader.class)
public class SpringSftpTest {

    @Autowired
    private CachingSessionFactory cachingSessionFactory;

    private RemoteFileTemplate<ChannelSftp.LsEntry> template;

    private String file1 = "a.txt";
    private String file2 = "b.txt";
    private String file3 = "c.txt";

    @Before
    public void init() {
        template = new RemoteFileTemplate<>(cachingSessionFactory);
        // create test file
        SftpTestUtils.createTestFiles(template, file1, file2, file3);
    }

    @After
    public void destroy() {
        // delete all temp file and folder
        SftpTestUtils.cleanUp(template, file1, file2, file3);
    }

    @Test
    public void testListFiles() {
        Assert.assertTrue(template.exists("/sftpTempFolder"));
        ChannelSftp.LsEntry[] lsEntries = template.list("/sftpTempFolder");
        // bypass '.' and '..' folder
        Arrays.stream(lsEntries).filter(lsEntry -> !lsEntry.getFilename().startsWith(".")).forEach(lsEntry -> {
            log.info(lsEntry.getLongname());
            Assert.assertTrue(lsEntry.getFilename().endsWith(".txt"));
        });
    }
}

這裏採用Spring-integration-sftp做sftp開發,jsch的話也一樣,配置好host、port等,連接到嵌入式sftp server即可。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章