Java SFTP服务器库?

43

有没有Java库可以用来实现SFTP服务器?

我想通过SFTP接收文件,但似乎找不到任何SFTP服务器的实现。我找到了FTP / SFTP / FTPS 客户端库和FTP / FTPS服务器库,但没有SFTP服务器。

澄清一下,我正在尝试通过SFTP 接收文件。而不是从我的应用程序中“获取”或“放置”文件到另一个现有服务器。

现在我的应用程序允许用户连接到本地Linux SFTP服务器,将文件放入其中,然后我的应用程序轮询目录,但我觉得这是一个不良的实现。我不喜欢“轮询”目录的想法,但不幸的是他们必须使用SFTP。 有什么建议吗?

7个回答

52

如何使用Apache Mina SSHD设置SFTP服务器:

public void setupSftpServer(){
    SshServer sshd = SshServer.setUpDefaultServer();
    sshd.setPort(22);
    sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser"));

    List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>();
    userAuthFactories.add(new UserAuthNone.Factory());
    sshd.setUserAuthFactories(userAuthFactories);

    sshd.setCommandFactory(new ScpCommandFactory());

    List<NamedFactory<Command>> namedFactoryList = new ArrayList<NamedFactory<Command>>();
    namedFactoryList.add(new SftpSubsystem.Factory());
    sshd.setSubsystemFactories(namedFactoryList);

    try {
        sshd.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

就是这样了。


这将创建一个SSH服务器,大多数现代客户端都会拒绝连接 :-( 请参见https://dev59.com/_pHea4cB1Zd3GeqPkA1H - Rich

6
请注意,SFTP不是SSL上的FTP,也不是SSH上的FTP。SFTP服务器支持需要在Java中实现SSHD。您最好使用Apache SSHD。

http://mina.apache.org/sshd-project/

我从未使用过SFTP,但我听说它很基础但功能齐全。


好观点。术语确实令人困惑。很多人认为SFTP是“安全FTP”,或者是运行在SSL或SSH上的FTP版本,或者其他什么(但它不是)。我希望他们能给它起一个完全不同的名字。 - hotshot309
链接已经失效了,顺便说一下。 - Tommy
1
FTP over SSL 被称为 FTPS,参见 https://de.wikipedia.org/wiki/FTP_%C3%BCber_SSL。 - Daniel
Mina SSHD 似乎已经迁移至 https://github.com/apache/mina-sshd - Michael Wyraz

2

我尝试在Windows上使用以上版本的MINA 0.10.1,并解决了一些问题,但我需要更好的身份验证和PK支持(仍不建议用于生产环境):

import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.PrintWriter;

import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;
import java.util.Scanner;

import java.math.BigInteger;

import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.interfaces.DSAPublicKey;

import java.security.KeyFactory;

import java.security.spec.KeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.RSAPublicKeySpec;

import org.apache.sshd.common.NamedFactory;

import org.apache.sshd.SshServer;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.apache.sshd.server.shell.ProcessShellFactory;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPassword;
import org.apache.sshd.server.auth.UserAuthPublicKey;

import org.apache.sshd.common.KeyExchange;
//import org.apache.sshd.server.kex.DHGEX;
//import org.apache.sshd.server.kex.DHGEX256;
import org.apache.sshd.server.kex.ECDHP256;
import org.apache.sshd.server.kex.ECDHP384;
import org.apache.sshd.server.kex.ECDHP521;
import org.apache.sshd.server.kex.DHG1;

import org.apache.mina.util.Base64;
/*
javac -classpath .;lib/sshd-core-0.10.1.jar;lib/mina-core-2.0.7.jar;lib/waffle-jna.jar;lib/guava-13.0.1.jar;lib/jna-platform-4.0.0.jar;lib/jna-4.0.0.jar SFTPServer.java
java -classpath .;lib/sshd-core-0.10.1.jar;lib/slf4j-simple-1.7.6.jar;lib/slf4j-api-1.6.6.jar;lib/mina-core-2.0.7.jar;lib/waffle-jna.jar;lib/guava-13.0.1.jar;lib/jna-platform-4.0.0.jar;lib/jna-4.0.0.jar SFTPServer
*/
public class SFTPServer {
  public void setupSftpServer() throws Exception {

    class AuthorizedKeyEntry {
      private String keyType;
      private String pubKey;

      private byte[] bytes;
      private int pos;
      private PublicKey key = null;

      private int decodeInt() {
        return ((bytes[pos++] & 0xFF) << 24) | ((bytes[pos++] & 0xFF) << 16)
                | ((bytes[pos++] & 0xFF) << 8) | (bytes[pos++] & 0xFF);
      }

      private BigInteger decodeBigInt() {
        int len = decodeInt();
        byte[] bigIntBytes = new byte[len];
        System.arraycopy(bytes, pos, bigIntBytes, 0, len);
        pos += len;
        return new BigInteger(bigIntBytes);
      }

      private void decodeType() {
        int len = decodeInt();
        keyType = new String(bytes, pos, len);
        pos += len;
      }

      public PublicKey getPubKey()  {
        return key;
      }

      public void setPubKey(PublicKey key) throws Exception {
        this.key = key;
        ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(byteOs);
        if (key instanceof RSAPublicKey) {
          keyType = "ssh-rsa";
          dos.writeInt(keyType.getBytes().length);
          dos.write(keyType.getBytes());

          RSAPublicKey rsakey = (RSAPublicKey)key;
          BigInteger e = rsakey.getPublicExponent();
          dos.writeInt(e.toByteArray().length);
          dos.write(e.toByteArray());
          BigInteger m = rsakey.getModulus();
          dos.writeInt(m.toByteArray().length);
          dos.write(m.toByteArray());
        } else if (key instanceof DSAPublicKey) {
          keyType = "ssh-dss";
          dos.writeInt(keyType.getBytes().length);
          dos.write(keyType.getBytes());

          DSAPublicKey dsskey = (DSAPublicKey)key;
          BigInteger p = dsskey.getParams().getP();
          dos.writeInt(p.toByteArray().length);
          dos.write(p.toByteArray());
          BigInteger q = dsskey.getParams().getQ();
          dos.writeInt(q.toByteArray().length);
          dos.write(q.toByteArray());
          BigInteger g = dsskey.getParams().getG();
          dos.writeInt(g.toByteArray().length);
          dos.write(g.toByteArray());
          BigInteger y = dsskey.getY();
          dos.writeInt(y.toByteArray().length);
          dos.write(y.toByteArray());
        } else {
          throw new IllegalArgumentException("unknown key encoding " + key.getAlgorithm());
        }
        bytes = byteOs.toByteArray();
        this.pubKey = new String(Base64.encodeBase64(bytes));
      }

      public void setPubKey(String pubKey) throws Exception {
        this.pubKey = pubKey;
        bytes = Base64.decodeBase64(pubKey.getBytes());
        if (bytes == null)
          return;
        decodeType();
        if (keyType.equals("ssh-rsa")) {
          BigInteger e = decodeBigInt();
          BigInteger m = decodeBigInt();
          KeySpec spec = new RSAPublicKeySpec(m, e);
          key = KeyFactory.getInstance("RSA").generatePublic(spec);
        } else if (keyType.equals("ssh-dss")) {
          BigInteger p = decodeBigInt();
          BigInteger q = decodeBigInt();
          BigInteger g = decodeBigInt();
          BigInteger y = decodeBigInt();
          KeySpec spec = new DSAPublicKeySpec(y, p, q, g);
          key = KeyFactory.getInstance("DSA").generatePublic(spec);
        } else {
          throw new IllegalArgumentException("unknown type " + keyType);
        }
      }
    }

    final SshServer sshd = SshServer.setUpDefaultServer();
    final Map<ServerSession, PublicKey> sessionKeys = new HashMap();

    class AuthorizedKeys extends HashMap<String,AuthorizedKeyEntry> {
      private File file;


      public void load(File file) throws Exception {
        this.file = file;
        Scanner scanner = new Scanner(file).useDelimiter("\n");
        while (scanner.hasNext())
          decodePublicKey(scanner.next());
        scanner.close();
      }

      public void save() throws Exception {
        PrintWriter w = new PrintWriter(file);
        for (String username : keySet()) {
          AuthorizedKeyEntry entry = get(username);
          w.print(entry.keyType + " " + entry.pubKey + " " + username + "\n");
        }
        w.close();
      }

      public void put(String username, PublicKey key) {
        AuthorizedKeyEntry entry = new AuthorizedKeyEntry();
        try {
          entry.setPubKey(key);
        } catch (Exception e) {
          e.printStackTrace();
        }
        super.put(username,entry);
      }

      private void decodePublicKey(String keyLine) throws Exception {
        AuthorizedKeyEntry entry = new AuthorizedKeyEntry();
        String[] toks = keyLine.split(" ");
        String username = toks[toks.length-1];
        for (String part : toks) {
          if (part.startsWith("AAAA")) {
            entry.setPubKey(part);
            //bytes = Base64.decodeBase64(part.getBytes());
            break;
          }
        }
        super.put(username,entry);
      }
    };

    final AuthorizedKeys authenticUserKeys = new AuthorizedKeys(); // load authorized_keys
    File file = new File("authorized_keys");
    file.createNewFile(); // create if not exists
    authenticUserKeys.load(file);


    sshd.setPort(22);
    sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("key.ser"));

    sshd.setShellFactory(new ProcessShellFactory(new String[] { "cmd.exe "}));

    sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
      public boolean authenticate(String username, String password, ServerSession session) {
        boolean authentic = false;
        try {
          new waffle.windows.auth.impl.WindowsAuthProviderImpl().logonUser(username,password);
          authentic = true;
          //authentic = username != null && username.equals(password+password); // obsecurity :)
          if (authentic) {
            PublicKey sessionKey = sessionKeys.get(session);
            if (sessionKey != null)
              authenticUserKeys.put(username, sessionKey); //save entry to authorized_keys
          }
        } catch (Exception e) {
          System.err.println(e);
        }
        return authentic;
      }
    });

    sshd.setPublickeyAuthenticator(new PublickeyAuthenticator() {
      public boolean authenticate(String username, PublicKey key, ServerSession session) {
        sessionKeys.put(session,key);
        return key.equals(authenticUserKeys.get(username).getPubKey());
      }
    });

    sshd.setUserAuthFactories(Arrays.<NamedFactory<UserAuth>>asList(
      new UserAuthPublicKey.Factory()
      ,new UserAuthPassword.Factory()));

    sshd.setCommandFactory(new ScpCommandFactory());

    sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(
      new SftpSubsystem.Factory()));

    //workaround for apache sshd 10.0+ (putty)
    sshd.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
      //new DHGEX256.Factory()
      //,new DHGEX.Factory()
      new ECDHP256.Factory()
      ,new ECDHP384.Factory()
      ,new ECDHP521.Factory()
      ,new DHG1.Factory()));

    Runtime.getRuntime().addShutdownHook(new Thread() {
      public void run() {
        try {
          authenticUserKeys.save();
          System.out.println("Stopping");
          sshd.stop();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });

    System.out.println("Starting");    
    try {
      sshd.start();
      Thread.sleep(Long.MAX_VALUE);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  static public void main(String[] args) throws Exception {
    new SFTPServer().setupSftpServer();
  }
}

1

看一下SSH工具(j2ssh)。它包括客户端和服务器。

然而,轮询目录并不是一个坏主意 - 它可能比使用j2ssh设置自己的SFTP服务器更可靠。我已经失去了遇到这种轮询的应用程序的数量,通常它们都运行得很好。


我不知道它以前是否提供过(开源)服务器,但现在似乎没有了。也许它已经被转移到他们的商业产品中了。 - Ross Drew

1

仅为完整性考虑 - 我们维护的SecureBlackbox库提供了在Java(包括Android)中创建自己的SSH / SFTP服务器的类。


链接已经失效,现在应该是https://www.nsoftware.com/sftp/sftpserver/。需要注意的是,此产品具有商业许可证,没有公开的定价信息。 - Michael Wyraz
1
@MichaelWyraz 不,那是另一个产品。SecureBlackbox还在,活得很好。我现在会更新链接。 - Eugene Mayevski 'Callback

-1

2
您提供的链接不是Java库,而是一个独立产品。此外,它似乎并没有使用SFTP,而是使用FTPS。您有阅读过关于这个产品的任何信息吗?还是只是在谷歌搜索“Java安全FTP”时选择了第二个链接? - CB.
6
非常抱歉,我点击了一个链接,上面说javasecureftpd实现了SFTP功能,所以我误以为它有这个功能。无论如何,你不必那么刻薄,我只是想帮助你。 - pakore

-2
我正在使用jftp http://j-ftp.sourceforge.net/。 从j-ftp-*.tgz/j-ftp/dist中提取jftp.jar。 唯一的问题是,他们将Apache类放在了jar包里(因此我必须手动删除common-httpclient、log4j包以避免冲突依赖)。

1
可能是因为问题涉及到SFTP服务器,而不是客户端。 - FelixM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接