2012-06-26 50 views
14

Recientemente, un cliente de nuestro equipo cambió inesperadamente algunos archivos importantes que recopilamos de un servidor ftp a sftp. Inicialmente tenía la impresión de que sería simple escribir o encontrar una utilidad java que pueda manejar sftp, definitivamente este no ha sido el caso. Lo que también ha agravado este problema es que estamos tratando de conectarnos al servidor sftp desde una plataforma de Windows (por lo que la definición de dónde se encuentra SSH_HOME en el cliente se confunde mucho).Ejemplo confiable de cómo usar SFTP usando autenticación de clave pública privada con Java

He estado utilizando la biblioteca apache-commons-vfs y he logrado obtener una solución que funciona confiablemente para la autenticación de nombre de usuario/contraseña, pero todavía no hay nada que pueda manejar confiablemente la autenticación de clave privada/pública.

El siguiente ejemplo funciona para la autenticación de nombre de usuario/contraseña, pero quiero ajustarlo para la autenticación de clave privada/pública.

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

Tengo mi clave privada almacenada en un lugar conocido y mi clave pública ha sido distrubuted al servidor (que hemos probado que estas teclas funcionan con éxito cuando se conecta utilizando otras herramientas)

tengo jugado un poco con la adición de la siguiente línea

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

Esto carga con éxito la clave privada en todo el marco, pero nunca a continuación, utiliza esa clave para autentificar aún más.

Cualquier ayuda o dirección más calurosa recibida

+0

¿No es 'ppk' una cosa masilla? ¿No deberías estar usando un 'pem'? – prodigitalson

+0

Posiblemente, pero no son servidores/clientes sftp independientes de cómo se crean las claves. Pensé que lo importante era que solo seguían el estándar open-ssh? – grumblebee

Respuesta

14

Después de mucho excavar alrededor fin llegué a la respuesta a mí mismo. Parece que gran parte de mi molestia fue hacer con el formato de la clave pública y privada

privateKey debe estar en formato openSSH publicKey por cualquier razón solo se puede pegar desde la ventana PuttyGen (exportar la clave pública siempre parecía dar con cabeceras que significa que el servidor de ventanas freeSSHd no podía usarlo)

de todos modos a continuación está mi código, finalmente, se me ocurrió que incluye el Javadoc, así que espero que debe guardar algunos otros el dolor que pasé

/** 
* 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); 
    } 
} 
+0

Solo una pregunta estúpida ... ¿Por qué hay una clave privada, pero debe ser la clave pública? – LStrike

+0

La clave pública está en el servidor (remoto). Emitirá un "desafío" cuando el cliente (local) intente conectarse. La clave privada se usa tanto para descifrar el desafío como para firmar la respuesta que luego se confirma con la clave pública (remota). Por lo tanto, la clave privada se usa localmente como una fuerte evidencia de que el usuario local es el correcto. – sofend

6

falta Esta publicación y respuesta fueron muy útiles, muchas gracias.

Solo quiero agregar una integración a la declaración "actualmente la biblioteca apache-commons-vfs2 no admite las frases de contraseña", ya que también lo hice con contraseña y funcionó.

Tiene que importar la biblioteca jsch en su proyecto (utilicé 0.1.49) e implementar la interfaz "com.jcraft.jsch.UserInfo".

Algo como esto debe estar bien:

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

Y entonces usted puede agregarlo a la SftpFileSystemConfigBuilder esta manera:

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

Espero que esto ayude.

+0

Este consejo para pasar la frase de contraseña ayudó mucho. Muchas gracias. –

+0

return true; en promptPassphrase() es extremadamente importante si tiene la frase de contraseña. Excelente respuesta! +1 – nilesh

4

supongo que esto es lo que busca -

/** 
* @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(); 
    } 
} 

Ver más en

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

+1

Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace de referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página vinculada cambia. –

+1

Se agregó fragmento de código y enlace para referencia –

Cuestiones relacionadas