Connecting to SFTP server using 2-Factor authentication In Java


In this article we will connect to SFTP server using both password and SSH key as the authentication method. This is also referred to as 2-Factor Authentication with SFTP.

We will use Apache's VFS library to establish the connection.

Following class shows how to:

  • Connect to SFTP server
  • Navigate to a desired folder
  • List of files in a folder

First we need to add the Apache VFS dependency to our project. For maven it can be added to the pom.xml file.

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-vfs2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-vfs2</artifactId>
<version>2.9.0</version>
</dependency>
Now let's look at the actual code, in SftpWithTwoFactorAuth class below.
/**
* This class uses Apache's VFS project to connect to remote SFTP server using
* 2-Factor authentication i.e username, password and also SSH key based authentication.
* <p>
* Maven Dependency: https://mvnrepository.com/artifact/org.apache.commons/commons-vfs2
* Apache Commons-VFS Project: https://commons.apache.org/proper/commons-vfs/
*/
public class SftpWithTwoFactorAuth {

public static final String SFTP = "sftp";

/**
* Holds instance of remote SFTP folder.
*/
private FileObject fileObject;

/**
* Direct instantiation not allowed. Use {@link #connect(String, Integer, String, String, File, String)}
* method to initiate connection.
*
* @param fileObject
*/
private SftpWithTwoFactorAuth(FileObject fileObject) {
this.fileObject = fileObject;
}

/**
* Initiate connection to remote SFTP server
*
* @param host
* @param port
* @param userName
* @param password - password to connect to remote SFTP
* @param sshKey - file containing the SSH private Key
* @param passphrase - Passphrase of SSH private key
* @return
*/
public static SftpWithTwoFactorAuth connect(String host,
Integer port,
String userName,
String password,
File sshKey,
String passphrase) throws URISyntaxException, FileSystemException {
URI sftpUri = createSftpUri(host, port, userName, password);

// Add SSK key as identity.
FileSystemOptions options = new FileSystemOptions();
SftpFileSystemConfigBuilder sftpConfigBuilder = SftpFileSystemConfigBuilder.getInstance();
IdentityProvider identityInfo = new IdentityInfo(sshKey, passphrase.getBytes());
sftpConfigBuilder.setIdentityProvider(options, identityInfo);

return new SftpWithTwoFactorAuth(VFS.getManager().resolveFile(sftpUri.toString(), options));
}

/**
* Create SFTP URI
*
* @param host
* @param port
* @param userName
* @param password
* @return
*/
private static URI createSftpUri(String host,
Integer port,
String userName,
String password) throws URISyntaxException {
String userInfo = userName + ":" + password;
return new URI(SFTP, userInfo, host, Objects.requireNonNullElse(port, -1), null, null, null);
}

/**
* Navigate to specific folder in SFTP
*
* @param folderPath
* @return
*/
public SftpWithTwoFactorAuth cd(String folderPath) throws FileSystemException {
if (this.fileObject == null) {
throw new RuntimeException("SFTP connection not initiated.");
}
FileObject newFolder = this.fileObject.resolveFile(folderPath);
if (!newFolder.exists() || !newFolder.isFolder()) {
throw new RuntimeException("Path does not exist or is not a folder.");
}
this.fileObject = newFolder;
return this;
}

/**
* List all file names. Only file names are listed, not the folders.
*
* @return
*/
public List<String> listFiles() throws FileSystemException {
return ofNullable(this.fileObject.findFiles(SELECT_FILES))
.map(Arrays::asList)
.orElseGet(Collections::emptyList)
.stream()
.map(FileObject::getName)
.map(FileName::getBaseName)
.collect(Collectors.toList());
}
}/**
* This class uses Apache's VFS project to connect to remote SFTP server using
* 2-Factor authentication i.e username, password and also SSH key based authentication.
* <p>
* Maven Dependency: https://mvnrepository.com/artifact/org.apache.commons/commons-vfs2
* Apache Commons-VFS Project: https://commons.apache.org/proper/commons-vfs/
*/
public class SftpWithTwoFactorAuth {

public static final String SFTP = "sftp";

/**
* Holds instance of remote SFTP folder.
*/
private FileObject fileObject;

/**
* Direct instantiation not allowed. Use {@link #connect(String, Integer, String, String, File, String)}
* method to initiate connection.
*
* @param fileObject
*/
private SftpWithTwoFactorAuth(FileObject fileObject) {
this.fileObject = fileObject;
}

/**
* Initiate connection to remote SFTP server
*
* @param host
* @param port
* @param userName
* @param password - password to connect to remote SFTP
* @param sshKey - file containing the SSH private Key
* @param passphrase - Passphrase of SSH private key
* @return
*/
public static SftpWithTwoFactorAuth connect(String host,
Integer port,
String userName,
String password,
File sshKey,
String passphrase) throws URISyntaxException, FileSystemException {
URI sftpUri = createSftpUri(host, port, userName, password);

// Add SSK key as identity.
FileSystemOptions options = new FileSystemOptions();
SftpFileSystemConfigBuilder sftpConfigBuilder = SftpFileSystemConfigBuilder.getInstance();
IdentityProvider identityInfo = new IdentityInfo(sshKey, passphrase.getBytes());
sftpConfigBuilder.setIdentityProvider(options, identityInfo);

return new SftpWithTwoFactorAuth(VFS.getManager().resolveFile(sftpUri.toString(), options));
}

/**
* Create SFTP URI
*
* @param host
* @param port
* @param userName
* @param password
* @return
*/
private static URI createSftpUri(String host,
Integer port,
String userName,
String password) throws URISyntaxException {
String userInfo = userName + ":" + password;
return new URI(SFTP, userInfo, host, Objects.requireNonNullElse(port, -1), null, null, null);
}

/**
* Navigate to specific folder in SFTP
*
* @param folderPath
* @return
*/
public SftpWithTwoFactorAuth cd(String folderPath) throws FileSystemException {
if (this.fileObject == null) {
throw new RuntimeException("SFTP connection not initiated.");
}
FileObject newFolder = this.fileObject.resolveFile(folderPath);
if (!newFolder.exists() || !newFolder.isFolder()) {
throw new RuntimeException("Path does not exist or is not a folder.");
}
this.fileObject = newFolder;
return this;
}

/**
* List all file names. Only file names are listed, not the folders.
*
* @return
*/
public List<String> listFiles() throws FileSystemException {
return ofNullable(this.fileObject.findFiles(SELECT_FILES))
.map(Arrays::asList)
.orElseGet(Collections::emptyList)
.stream()
.map(FileObject::getName)
.map(FileName::getBaseName)
.collect(Collectors.toList());
}
}
Sample usage of the SftpWithSshKey class would be as below
/**
* Sample usage of {@link SftpWithTwoFactorAuth} class
*/
class SftpWithTwoFactorAuthTest {

/**
* Test that all files in documents folder are listed.
*
* @throws URISyntaxException
* @throws FileSystemException
*/
public void listAllFiles() throws URISyntaxException, FileSystemException {
File privateKey = new File("~/.ssh/ida_rsa");
List<String> files = SftpWithTwoFactorAuth.connect("localhost", 22, "admin", "password", privateKey, "passphrase")
.cd("./documents")
.listFiles();
Assertions.assertEquals(2, files.size());
}
}

0 comments:

Post a Comment