Sadot Hernández

Spring Boot + SFTP JSCH

PublicadoAugust 21, 2021

Java: ¿Cómo subir archivos a un servidor SFTP?

image

1. Información General

En este tutorial rápido, veremos cómo podemos realizar la carga archivos mediante Spring Boot, y SFTP JSCH.

2.- Dependencias de Maven

Para crear este ejemplo, usaremos las bibliotecas Spring Framework junto con la biblioteca SFTP JSCH.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.4</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.zademy</groupId>
	<artifactId>upload.files.sftp</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>upload.files.sftp</name>
	<description>Spring Boot SFTP JSCH</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.jcraft</groupId>
			<artifactId>jsch</artifactId>
			<version>0.1.55</version>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

3.- Estructura del Proyecto

4.- Archivo application. yml

Para nuestro archivo application.yml tendremos las siguientes propiedades con los datos para poder establecer nuestra conexión con nuestro servidor SFTP.

sftp.connection:
  host: 55.56.15.9
  port: 22
  user: linux
  password: bxsfertTPzuqiP

5.- Interface SFTPUtils

En nuestra interface SFTPUtils tendremos tres métodos principales para poder subir, checar y eliminar un archivo.

/**
 * The Interface SFTPUtils.
 */
public interface SFTPUtils {


	/**
	 * Upload file to SFTP.
	 *
	 * @param filename the filename
	 * @param fis the fis
	 * @param destinationDir the destination dir
	 * @return the string
	 */
	public String uploadFileToSFTP(String filename, InputStream fis, String destinationDir);

	/**
	 * Check exist.
	 *
	 * @param fileName the file name
	 * @param destinationDir the destination dir
	 * @return true, if successful
	 */
	public boolean checkExist(String fileName, String destinationDir);


	/**
	 * Delete file.
	 *
	 * @param fileName the file name
	 * @param destinationDir the destination dir
	 */
	public void deleteFile(String fileName, String destinationDir);

}  

6.- Implementación SFTPUtilsImpl

En la implementación podremos apreciar los 3 métodos mencionados anteriormente, así como dos métodos privados, uno de ellos “initSessions” el cual se encargará de realizar la conexión con nuestro servidor SFTP y el otro método “validateDirectory” que nos ayudara a validar el directorio al cual deseamos subir nuestro archivo y en caso de que no encuentre el directorio al momento de cachar la excepción este directorio se creara por lo que nuestro archivo se subirá sin ningún problema.

Tener en cuenta que para que podamos subir o crear un directorio necesitamos tener permisos sobre el directorio ya sea con Windows o Linux.

/**
 * The Class SFTPUtilsImpl.
 */
@Component
public class SFTPUtilsImpl implements SFTPUtils {

	/** The logger. */
	private Logger logger = LoggerFactory.getLogger(SFTPUtilsImpl.class);

	/** The host. */
	@Value("${sftp.connection.host}")
	private String host;

	/** The port. */
	@Value("${sftp.connection.port}")
	private Integer port;

	/** The user. */
	@Value("${sftp.connection.user}")
	private String user;

	/** The password. */
	@Value("${sftp.connection.password}")
	private String password;

	/** The channel sftp. */
	private ChannelSftp channelSftp;

	/** The channel. */
	private Channel channel;

	/** The session. */
	private Session session;

	/**
	 * Instantiates a new SFTP utils.
	 */
	public SFTPUtilsImpl() {
		super();
	}

	/**
	 * Inits the session.
	 */
	public void initSession() {

		try {

			JSch jsch = new JSch();

			logger.info("host: {}", host);
			logger.info("port: {}", port);
			logger.info("user: {}", user);
			logger.info("pass: {}", password);

			session = jsch.getSession(user, host, port);

			session.setPassword(password);

			Properties config = new Properties();

			config.put("StrictHostKeyChecking", "no");

			session.setConfig(config);

		} catch (Exception ex) {

			logger.error("Error", ex);

		}

	}

	/**
	 * Upload file to FTP.
	 *
	 * @param filename       the filename
	 * @param fis            the fis
	 * @param destinationDir the destination dir
	 * @return the string
	 */
	public String uploadFileToSFTP(String filename, InputStream fis,  String destinationDir) {

		String result = "";

		initSession();

		try {

			if (!session.isConnected()) {
				
				session.connect();
				
			}	

			channel = session.openChannel("sftp");

			channel.connect();

			channelSftp = (ChannelSftp) channel;

			validateDirectory(destinationDir);

			channelSftp.put(fis, filename);

			logger.info("Upload successful portfolio file name: {}", filename);
			result = String.format("sftp://%s/%s/%s", host, destinationDir, filename);

			channelSftp.exit();
			channel.disconnect();
			session.disconnect();

		} catch (SftpException | JSchException e) {

			logger.error("Error -> ", e);

		}

		return result;
	}

	/**
	 * Check exist.
	 *
	 * @param fileName       the file name
	 * @param destinationDir the destination dir
	 * @return true, if successful
	 */
	public boolean checkExist(String fileName, String destinationDir) {

		boolean existed = false;

		initSession();

		try {

			if (!session.isConnected()) {
				
				session.connect();
				
			}	

			channel = session.openChannel("sftp");
			channel.connect();
			channelSftp = (ChannelSftp) channel;

			validateDirectory(destinationDir);

			Vector ls = channelSftp.ls(destinationDir);

			if (ls != null) {

				// Iterate listing.
				logger.info(fileName);

				for (int i = 0; i < ls.size(); i++) {

					LsEntry entry = (LsEntry) ls.elementAt(i);
					
					String fileNameAux = entry.getFilename();

					if (!entry.getAttrs().isDir()) {

						if (fileName.toLowerCase().startsWith(fileNameAux)) {
							existed = true;
						}

					}

				}

			}

			channelSftp.exit();
			channel.disconnect();
			session.disconnect();

		} catch (SftpException | JSchException e) {

			existed = false;

			if (session.isConnected()) {

				session.disconnect();

			}

		}

		return existed;
	}

	/**
	 * Delete file.
	 *
	 * @param fileName       the file name
	 * @param destinationDir the destination dir
	 */
	public void deleteFile(String fileName, String destinationDir) {

		initSession();

		try {

			if (!session.isConnected()) {
				
				session.connect();
				
			}	

			channel = session.openChannel("sftp");
			channel.connect();
			
			channelSftp = (ChannelSftp) channel;
			channelSftp.cd(destinationDir);
			channelSftp.rm(fileName);
			
			logger.info("File Delete");
			
			channelSftp.exit();
			
			channel.disconnect();
			session.disconnect();

		} catch (SftpException | JSchException e) {

			logger.info(e.getMessage());

			if (session.isConnected()) {
				
				session.disconnect();
				
			}

		}

	}

	/**
	 * Validate directory.
	 *
	 * @param destinationDir the destination dir
	 * @throws SftpException the sftp exception
	 */
	private void validateDirectory(String destinationDir) throws SftpException {

		try {

			channelSftp.cd(destinationDir);

			logger.info("cd relative Dir");

		} catch (SftpException e) {
			
			//If the directory is not located, it is created
			channelSftp.mkdir(destinationDir);
			channelSftp.cd(destinationDir);

		}

	}

}

7.- Clase Principal

Ahora mandataremos a llamar inyectaremos nuestro componente SFTPUtils como se muestra a continuación y podremos ver las 3 implementaciones de subir, checar y eliminar un archivo.

/**
 * The Class Application.
 */
@SpringBootApplication
public class Application implements CommandLineRunner {

	/** The logger. */
	private Logger logger = LoggerFactory.getLogger(Application.class);

	/** The sftp utils. */
	@Autowired
	private SFTPUtils sftpUtils;

	/**
	 * The main method.
	 *
	 * @param args the arguments
	 */
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	/**
	 * Run.
	 *
	 * @param args the args
	 * @throws Exception the exception
	 */
	@Override
	public void run(String... args) throws Exception {

		String destinationDir = "/home/ubuntu/Temporales";

		// Get File
		File file = new File("C:\\aow_drv.log");

		// Get File Imagen
		File fileImage = new File("C:\\wallpapers.jpg");

		FileInputStream fin;

		FileInputStream finImage;

		String result = "";

		try {

			// Upload File
			fin = new FileInputStream(file);

			result = sftpUtils.uploadFileToSFTP("aow_drv.log", fin, destinationDir);

			logger.info("File upload: {}", result);

			// Upload Image
			finImage = new FileInputStream(fileImage);

			result = sftpUtils.uploadFileToSFTP("wallpapers.jpg", finImage, destinationDir);

			logger.info("Image upload: {}", result);
			
			//Exist File
			if(sftpUtils.checkExist("wallpapers.jpg", destinationDir)) {
				
				logger.info("File Exist -> wallpapers.jpg");
				
			}
			
			// Delete File
			sftpUtils.deleteFile("aow_drv.log", destinationDir);

		} catch (FileNotFoundException | SecurityException | NullPointerException e) {
			logger.error("File not found", e);
		}

	}

}

Ahora ya podemos subir archivos con nuestro proyecto creado a un servidor SFTP mediante Spring Boot, y SFTP JSCH.

Una versión funcional del código que se muestra en este tutorial está disponible en Gitlab.

Quiero aclarar que esta versión del código es una modificación en base a la versión de DNHOME

Referencias

Delgado, C. (29 de Enero de 2016). ourcodeworld. Obtenido de https://ourcodeworld.co/: https://ourcodeworld.co/articulos/leer/30/como-subir-un-archivo-a-un-servidor-con-la-libreria-sftp-jsch-en-java

DNHOME. (25 de Junio de 2012). dnhome. Obtenido de https://dnhome.wordpress.com/: https://dnhome.wordpress.com/2012/06/25/java-upload-file-to-sftp-server/