Надежный пример использования SFTP с использованием аутентификации с использованием открытого ключа с помощью Java

Недавно клиент из нашего неожиданно переместил некоторые важные файлы, которые мы собираем с ftp на sftp-сервер. Первоначально у меня создалось впечатление, что было бы просто написать или найти утилиту java, которая может обрабатывать sftp, это определенно не доказано. Что также усугубляет эту проблему, так это то, что мы пытаемся подключиться к серверу sftp с платформы Windows (поэтому определение того, где SSH_HOME находится на клиенте, очень запутано).

Я использую библиотеку apache-commons-vfs и сумел получить решение, которое надежно работает для аутентификации имени пользователя и пароля, но пока ничего не может надежно обрабатывать проверку подлинности с открытым/открытым ключом.

Следующий пример работает для аутентификации имени пользователя и пароля, но я хочу настроить его для проверки подлинности с открытым/открытым ключом.

public static void sftpGetFile(String server, String userName,String password, 
        String remoteDir, String localDir, String fileNameRegex)
    {

       File localDirFile  = new File(localDir);
       FileSystemManager fsManager = null;

       if (!localDirFile.exists()) {
           localDirFile.mkdirs();
       }

       try {
           fsManager = VFS.getManager();
       } catch (FileSystemException ex) {
           LOGGER.error("Failed to get fsManager from VFS",ex);
           throw new RuntimeException("Failed to get fsManager from VFS", ex);
       }

       UserAuthenticator auth = new StaticUserAuthenticator(null, userName,password);

       FileSystemOptions opts = new FileSystemOptions();

       try {
           DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts,
                   auth);
       } catch (FileSystemException ex) {
           LOGGER.error("setUserAuthenticator failed", ex);
           throw new RuntimeException("setUserAuthenticator failed", ex);
       }
       Pattern filePattern = Pattern.compile(fileNameRegex);
       String startPath = "sftp://" + server + remoteDir;
       FileObject[] children;

       // Set starting path on remote SFTP server.
       FileObject sftpFile;
       try {
           sftpFile = fsManager.resolveFile(startPath, opts);

           LOGGER.info("SFTP connection successfully established to " +
                   startPath);
       } catch (FileSystemException ex) {
           LOGGER.error("SFTP error parsing path " +
                   remoteDir,
                   ex);

           throw new RuntimeException("SFTP error parsing path " +
                   remoteDir,
                   ex);
       }

       // Get a directory listing
       try {
           children = sftpFile.getChildren();
       } catch (FileSystemException ex) {
           throw new RuntimeException("Error collecting directory listing of " +
                   startPath, ex);
       }

       search:
       for (FileObject f : children) {
           try {
               String relativePath =
                       File.separatorChar + f.getName().getBaseName();

               if (f.getType() == FileType.FILE) {
                   System.out.println("Examining remote file " + f.getName());

                   if (!filePattern.matcher(f.getName().getPath()).matches()) {
                       LOGGER.info("  Filename does not match, skipping file ." +
                               relativePath);
                       continue search;
                   }

                   String localUrl = "file://" + localDir + relativePath;
                   String standardPath = localDir + relativePath;
                   System.out.println("  Standard local path is " + standardPath);
                   LocalFile localFile =
                           (LocalFile) fsManager.resolveFile(localUrl);
                   System.out.println("    Resolved local file name: " +
                           localFile.getName());

                   if (!localFile.getParent().exists()) {
                       localFile.getParent().createFolder();
                   }

                   System.out.println("  ### Retrieving file ###");
                   localFile.copyFrom(f,
                           new AllFileSelector());
               } else {
                   System.out.println("Ignoring non-file " + f.getName());
               }
           } catch (FileSystemException ex) {
               throw new RuntimeException("Error getting file type for " +
                       f.getName(), ex);
           }

       }

       FileSystem fs = null;
       if (children.length > 0) {
           fs = children[0].getFileSystem(); // This works even if the src is closed.
           fsManager.closeFileSystem(fs);
       }
    }

У меня есть закрытый ключ, хранящийся в известном месте, и мой открытый ключ был распределен на сервере (мы протестировали, что эти ключи работают успешно при подключении с помощью других инструментов)

Я играл с добавлением следующей строки

SftpFileSystemConfigBuilder.getInstance().setIdentities(this.opts, new File[]{new File("c:/Users/bobtbuilder/.ssh/id_dsa.ppk")});

Это успешно загружает закрытый ключ во всю инфраструктуру, но никогда не использует этот ключ для последующей аутентификации.

Любая помощь или направление, наиболее тепло получившие

Ответы

Ответ 1

После долгих копаний я наконец добрался до самого ответа. Похоже, что большая часть моей работы связана с форматом частного и открытого ключа

privateKey должен быть в формате openSSH publicKey по какой-либо причине может быть вставлен только из окна puttyGen (экспорт открытого ключа всегда казался ему отсутствующим заголовком, что означает, что сервер windows freeSSHD не смог его использовать)

В любом случае ниже мой код, который я, наконец, придумал, включая javadoc, поэтому, надеюсь, вам следует сохранить некоторые другие боли, которые я пережил.

/**
* Fetches a file from a remote sftp server and copies it to a local file location.  The authentication method used
* is public/private key authentication. <br><br>

* IMPORTANT: Your private key must be in the OpenSSH format, also it must not have a passphrase associated with it.
*    (currently the apache-commons-vfs2 library does not support passphrases)<p>
* 
* Also remember your public key needs to be on the sftp server.  If you were connecting as user 'bob' then your
* public key will need to be in '.ssh/bob' on the server (the location of .ssh will change depending on the type
* of sftp server)
* 
* @param server The server we care connection to 
* @param userName The username we are connection as
* @param openSSHPrivateKey The location of the  private key (which must be in openSSH format) on the local machine
* @param remoteDir The directory from where you want to retrieve the file on the remote machine (this is in reference to SSH_HOME, SSH_HOME is the direcory you 
* automatically get directed to when connecting)
* @param remoteFile The name of the file on the remote machine to be collected (does not support wild cards)
* @param localDir The direcoty on the local machine where you want the file to be copied to
* @param localFileName The name you wish to give to retrieved file on the local machine
* @throws IOException - Gets thrown is there is any problem fetching the file
*/
public static void sftpGetFile_keyAuthentication(String server, String userName, String openSSHPrivateKey,
    String remoteDir,String remoteFile, String localDir, String localFileName) throws IOException
{

   FileSystemOptions fsOptions = new FileSystemOptions();
   FileSystemManager fsManager = null;
   String remoteURL = "sftp://" + userName + "@" + server + "/" + remoteDir + "/" + remoteFile;
   String localURL  = "file://" + localDir + "/" + localFileName;

    try {
        SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(fsOptions, "no");
        SftpFileSystemConfigBuilder.getInstance().setIdentities(fsOptions, new File[]{new File(openSSHPrivateKey)});
        fsManager = VFS.getManager();
        FileObject remoteFileObject = fsManager.resolveFile(remoteURL, fsOptions);
        LocalFile localFile =
                   (LocalFile) fsManager.resolveFile(localURL);
        localFile.copyFrom(remoteFileObject,
                   new AllFileSelector());
    } catch (FileSystemException e) {
        LOGGER.error("Problem retrieving from " + remoteURL + " to " + localURL,e );
        throw new IOException(e);
    }
}

Ответ 2

Это сообщение и ответ были очень полезными, спасибо вам большое.

Я просто хочу добавить интеграцию в оператор "в настоящее время библиотека apache-commons-vfs2 не поддерживает кодовые фразы", ​​как я сделал это также с кодовой фразой, и она сработала.

Вам нужно импортировать библиотеку jsch в свой проект (я использовал 0.1.49) и реализовать интерфейс "com.jcraft.jsch.UserInfo".

Что-то вроде этого должно быть хорошо:

public class SftpUserInfo implements UserInfo {

    public String getPassphrase() {
        return "yourpassphrase";
    }

    public String getPassword() {
        return null;
    }

    public boolean promptPassphrase(String arg0) {
        return true;
    }

    public boolean promptPassword(String arg0) {
        return false;
    }
}

И затем вы можете добавить его в SftpFileSystemConfigBuilder следующим образом:

SftpFileSystemConfigBuilder.getInstance().setUserInfo(fsOptions, new SftpUserInfo());

Надеюсь, что это поможет.

Ответ 3

Я думаю, это то, что вы ищете -

/**
* @param args
*/
public static void main(String[] args) {

    /*Below we have declared and defined the SFTP HOST, PORT, USER
            and Local private key from where you will make connection */
    String SFTPHOST = "10.20.30.40";
    int    SFTPPORT = 22;
    String SFTPUSER = "kodehelp";
    // this file can be id_rsa or id_dsa based on which algorithm is used to create the key
    String privateKey = "/home/kodehelp/.ssh/id_rsa";
    String SFTPWORKINGDIR = "/home/kodehelp/";

    JSch jSch = new JSch();
    Session     session     = null;
    Channel     channel     = null;
    ChannelSftp channelSftp = null;
    try {
        jSch.addIdentity(privateKey);
        System.out.println("Private Key Added.");
        session = jSch.getSession(SFTPUSER,SFTPHOST,SFTPPORT);
        System.out.println("session created.");

        java.util.Properties config = new java.util.Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config);
        session.connect();
        channel = session.openChannel("sftp");
        channel.connect();
        System.out.println("shell channel connected....");
        channelSftp = (ChannelSftp)channel;
        channelSftp.cd(SFTPWORKINGDIR);
        System.out.println("Changed the directory...");
    } catch (JSchException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (SftpException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }finally{
        if(channelSftp!=null){
            channelSftp.disconnect();
            channelSftp.exit();
        }
        if(channel!=null) channel.disconnect();

        if(session!=null) session.disconnect();
    }
}

Подробнее в

http://kodehelp.com/sftp-connection-public-key-authentication-java/