Propòsit
El servei de SFTP de Canigó permet enviar i rebre arxius entre el servidor on s’executa l’aplicació a altres servidors de forma segura mitjançant l’intercanvi de claus.
El servei està basat en les llibreries JSCH i Commons-VFS, la primera es tracta d’un projecte open source que permet la connexió via SSH a qualsevol màquina. La segona llibrería és un projecte també open_source de la Apache Foundation que permet treballar amb més facilitat amb la JSCH, donant eines per crear connexions SFTP (entre d’altres) contra un servidor.
Documents i Fonts de Referència
Nom | URL |
---|---|
JSCH | http://www.jcraft.com/jsch/ |
Commons VFS | http://commons.apache.org/vfs/ |
Glossari
SFTP
Secure File Transfer Protocol (Protocol de Transferència Segura d’Arxius), es tracta d’un protocol de transferència de fitxers entre computadors de forma segura.
Download
Rebre arxius des d’un servidor.
Upload
Enviar arxius a un servidor.
Socket
Port del servidor habilitat per realitzar la transferència de fitxers/streams mitjançant el protocol SFTP.
Instal.lació i Configuració
Instal.lació
Per tal d’instal·lar el mòdul de MongoDB s’ha d’afegir manualment la següent dependència en el fitxer pom.xml
de l’aplicació:
<dependency>
<groupId>cat.gencat.ctti</groupId>
<artifactId>canigo.support.sftp</artifactId>
<version>${canigo.support.sftp.version}</version>
</dependency>
A la Matriu de Compatibilitats es pot comprovar la versió del mòdul compatible amb la versió de Canigó utilitzada.
Configuració
La configuració ha passat a realitzar-se amb fitxer yml en lloc d’amb el fitxer <PROJECT_ROOT>/src/main/resources/config/props/sftp.properties
.
Un exemple del contingut del fitxer application.yml podria ser el següent:
sftp:
url: URL_DEL_SERVIDOR_FTP
port: 22
username: USUARI_FTP
password: PASSWORD_FTP
Indiquem taula amb explicació de les propietats disponibles:
Propietat | Requerit | Descripció |
---|---|---|
url | Sí | Host del servidor sftp. |
port | No | Port del servidor sftp. Valor per defecte: 22. |
username | No | Usuari de connexió al servidor de secure ftp (sftp). |
password | No | Password de l’usuari de connexió. |
Els arxius de configuració que contenen els beans del mòdul i que serán carregats per Spring, son automàticament registrats pel core de Canigó, per lo que el desenvolupador no ha de definir cap arxiu XML per aixecar el servei.
Utilització del Mòdul
Per accedir al mòdul de SFTP, el desenvolupador pot realitzar una crida de forma externa mitjançant el patró ‘Dependency Injection’.
Podem diferenciar dues formes d’injectar-ho:
- Per XML:
<bean id="tractamentClients" class="cat.gencat.demo.mbean.TractamentClient">
<property name="sftpService" ref="sftpService" />
</bean>
La clase TractamentClient tindria la següent estructura:
import cat.gencat.ctti.canigo.arch.support.sftp.SftpService;
public class TractamentClient{
private SftpService sftpService;
public void setSftpService(SftpService service){
this.sftpService = service;
}
public void execute(){
sftpService.login();
}
}
Injecció del mòdul de configuració dins del bean “tractamentClients”. Spring s’encarregarà d’injectar el mòdul de SFTP executant el mètode setSftpService.
- Per anotacions:
import cat.gencat.ctti.canigo.arch.support.sftp.SftpService;
@Component("tractamentClients")
public class TractamentClient{
@Autowired
private SftpService sftpService;
public void execute(){
sftpService.login();
}
}
Realitzar connexió
Tenim diferents maneres de realitzar la connexió:
- Els paràmetres de connexió venen informats a l’arxiu de propietats. El mòdul de SFTP farà servir aquestes propietats per gestionar la connexió al servidor.
sftpService.login();
- Si no hem indicat a través de la configuració l’usuari i la contrasenya per realitzar la connexió, s’indicarà manualment en al crida.
sftpService.login(usuari,contrasenya);
- Servidor, usuari, contrasenya i port no informat.
sftpService.login(usuari, contrasenya, ftpUrl, ftpPort);
Per realitzar una connexió al servidor a través d’un proxy cal informar les següents propietats a l’arxiu de configuració del mòdul:
- sftp.proxyType: Tipus de proxy, pot tenir els valors http o socks.
- sftp.proxyHost: Adreça del host.
- sftp.proxyPort: Port del host.
Realitzar desconnexió del servidor SFTP
Per realitzar la desconnexió del servidor FTP seguirem un patró com el mostrat en el següent exemple:
- Validem que estem conectats.
- Desconectem en el cas de estar logats al servidor.
if (sftpService.isLogged()) {
sftpService.logout();
}
Realitzar download d’un fitxer
Per realitzar un download d’un fitxer seguirem un patró com el mostrat en el següent exemple:
sftpService.login();
if (sftpService.isLogged()) {
sftpService.downloadFile(fileName, localPath, remotePath);
sftpService.logout();
}
Realitzarem doncs els següents passos:
- Cridem al mètode login() per realitzar la connexió i l’autentificació al servidor remot mitjançant el protocol SFTP.
- En cas de que la connexió al servidor remot s’hagi realitzat correctament es crida al mètode downloadFile() per realitzar el download del fitxer del servidor remot.
- Realitzar la desconnexió del servidor SFTP.
Es crida el mètode downloadFile() que amb els següents paràmetres:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Si | String | Nom del fitxer del servidor remot que es vol realitzar el download. |
2 | Si | String | Directori local on es vol guardar el fitxer. |
3 | Si | String | Directori remot on es troba el fitxer a baixar. |
I retorna:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Si | boolean | TRUE o FALSE segons s’ha realitzat correcta o incorrectament el procés. |
Realitzar upload d’un fitxer
Per realitzar un upload d’un fitxer seguirem un patró com el mostrat en el següent exemple:
sftpService.login();
if (sftpService.isLogged()) {
sftpService.uploadFile(fileName, localPath, remotePath);
sftpService.logout();
}
Realitzarem doncs els següents passos:
- Cridem al mètode login() per realitzar la connexió i l’autentificació al servidor remot mitjançant el protocol SFTP.
- En cas de que la connexió al servidor remot s’hagi realitzat correctament es crida al mètode uploadFile() per realitzar el upload del fitxer del servidor local al servidor remot.
- Realitzar la desconnexió del servidor SFTP.
Es crida el mètode uploadFile() que amb els següents paràmetres:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Si | String | Nom del fitxer del servidor remot que es vol realitzar el upload. |
2 | Si | String | Directori local on es troba el fitxer a pujar. |
3 | Si | String | Directori remot on es vol guardar el fitxer. |
Obtenir la llista de noms dels fitxers del servidor remot
Per obtenir la llista de fitxers en una carpeta d’un servidor remot seguirem un patró com el mostrat en el següent exemple:
sftpService.login();
if (sftpService.isLogged()) {
FileObject[] listFiles = sftpService.listFiles(remotePath);
sftpService.logout();
}
Realitzarem doncs els següents passos:
- Cridem al mètode login() per realitzar la connexió i l’autentificació al servidor remot mitjançant el protocol SFTP.
- En cas de que la connexió al servidor remot s’hagi realitzat correctament es crida al mètode listFiles() per obtenir una llista de fitxers en una carpeta d’un servidor remot.
Es crida el mètode listFiles() que amb els següents paràmetres:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Sí | String | Nom de la carpeta del servidor remot d’on es vol obtenir la llista de fitxers. |
El mètode retorna un array de FileObject, interfícies de la llibrería Commons VFS que ens donen una sèrie de mètodes per a treballar amb els fitxers remots.
Crear un fitxer al servidor remot
Per crear un fitxer al servidor remot seguirem un patró com el mostrat en el següent exemple:
sftpService.login();
if (sftpService.isLogged()) {
createFile(fileName, remotePath);
sftpService.logout();
}
Realitzarem doncs els següents passos:
- Cridem al mètode login() per realitzar la connexió i l’autentificació al servidor remot mitjançant el protocol SFTP.
- En cas de que la connexió al servidor remot s’hagi realitzat correctament es crida al mètode createFile() per crear el fitxer en el servidor remot.
Es crida el mètode createFile() que amb els següents paràmetres:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Si | String | Nom del fitxer a crear en el servidor remot. |
2 | Si | String | Directori remot on es vol crear el fitxer. |
El mètode retorna un boolean TRUE o FALSE segons s’ha realitzat correcta o incorrectament el procés.
Crear un directori al servidor remot
Per crear un directori al servidor remot seguirem un patró com el mostrat en el següent exemple:
sftpService.login();
if (sftpService.isLogged()) {
createFolder(folderName, remotePath);
sftpService.logout();
}
Realitzarem doncs els següents passos:
- Cridem al mètode login() per realitzar la connexió i l’autentificació al servidor remot mitjançant el protocol SFTP.
- En cas de que la connexió al servidor remot s’hagi realitzat correctament es crida al mètode createFolder() per crear el fitxer en el servidor remot.
Es crida el mètode createFolder() que amb els següents paràmetres:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Si | String | Nom del directori a crear en el servidor remot. |
2 | Si | String | Directori remot on es vol crear el directori. |
El mètode retorna un boolean TRUE o FALSE segons s’ha realitzat correcta o incorrectament el procés.
Esborrar un fitxer al servidor remot
Per esborrar un fitxer al servidor remot seguirem un patró com el mostrat en el següent exemple:
sftpService.login();
if (sftpService.isLogged()) {
deleteFile(fileName, remotePath);
sftpService.logout();
}
Realitzarem doncs els següents passos:
- Cridem al mètode login() per realitzar la connexió i l’autentificació al servidor remot mitjançant el protocol SFTP.
- En cas de que la connexió al servidor remot s’hagi realitzat correctament es crida al mètode deleteFile() per crear el fitxer en el servidor remot.
Es crida el mètode deleteFile() que amb els següents paràmetres:
Ordre | Requerit | Tipus | Descripció |
---|---|---|---|
1 | Si | String | Nom del fitxer a esborrar en el servidor remot. |
2 | Si | String | Directori remot on es troba el fitxer. |
El mètode retorna un boolean TRUE o FALSE segons s’ha realitzat correcta o incorrectament el procés.
Acttualitzacions realitzades per adaptar el mòdul Canigó Support SFTP per a Java 17.
A continuació indicarem les modificacions realitzades, a causa dels diferents canvis de dependències,com ara: Junit 4 a Junit Jupiter, Javax a Jakarta i altres modificacions rellevants perquè continuï sent compatible amb la corresponent versió de Spring i JAVA 17.
-
Els principals canvis de l’actualització realitzada , ha estat per part de Junit 4 a JUnit Jupiter. A continuació vam procedir a indicar aquests canvis perquè fossin compatibles amb Java 17 i Spring 3.1.4:
- Anotació @RunWith
Hem de reemplaçar l’anotació @RunWith(SpringJUnit4ClassRunner.class) amb
l’anotació @ExtendWith(SpringExtension.class).
Per a aquest canvi es reemplacen les dependències actuals
per aquestes:
import org.junit.runner.RunWith; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.junit.jupiter.SpringExtension;
La classe SpringExtension és proporcionada per Spring 5 i integra l’Spring TestContext Framework amb JUnit 5. Aquesta anotació és reiterativa en la majoria de les classes i interfícies on s’utilitzin al llarg del mòdul de Canigó Support SFTP
- Exemple
import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) @ContextConfiguration(locations = { "/cat/gencat/ctti/canigo/arch/core/config/canigo-core.xml" }) public class SftpServiceMockTest {}
- Anotació @RunWith
Hem de reemplaçar l’anotació @RunWith(SpringJUnit4ClassRunner.class) amb
l’anotació @ExtendWith(SpringExtension.class).
Per a aquest canvi es reemplacen les dependències actuals
-
Anotació @Before Hem de reemplaçar l’anotació @Before amb l’anotació BeforeEach. Per a aquest canvi es reemplacen les dependències actuals
import org.junit.Before;
per aquestes:
import org.junit.jupiter.api.BeforeEach;
Aquesta anotació és reiterativa en la majoria de les classes i interfícies on s’utilitzin al llarg del mòdul de Canigó Support SFTP . Cal tenir en compte que el mètode ha de ser públic perquè pugui ser usada.
-
Exemple
import org.junit.jupiter.api.BeforeEach; @BeforeEach public void setUp() { localPath = new File(this.getClass().getResource("/config/props/" + FILENAME).getFile()).getParentFile().getAbsolutePath(); ReflectionTestUtils.setField(sftpServiceMock, "manager", manager); sftpServiceImplSets(YES_STRICT_HOST_KEY_CHECKING, KNOWN_HOSTS, IDENTITY_PATH, HTTP_PROXY_TYPE, LOCALHOST_PROXY_HOST, PORT_PROXY_HOST); }
Tests Unitaris
Un exemple d’utilització del mòdul de SFTP són els tests unitaris.
S’ha de tenir en compte que s’ha de disposar d’accés a un servidor remot mitjançant el protocol SFTP per poder realitzar els tests.
sftp.properties
/**
* Unit test for mailing service
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"../../core/config/canigo-core.xml"})
public class SftpServiceReleaseTest{
// File exchange
private String fileName = null;
private String localPath = null;
private String remotePath = null;
private String tempName = null;
@Autowired
@Qualifier("sftpService")
SftpService sftpService;
@Before
public void setUp() throws AddressException{
fileName = "sftp.properties";
remotePath = "/home/continuum/tmp";
localPath = this.getClass().getResource("/temp/arxiu_pujar.pdf").getPath();
localPath = localPath.substring(0, (localPath.lastIndexOf("/")+1));
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testLogin() throws Exception {
try {
Assert.assertTrue(sftpService.login());
if (sftpService.isLogged()) {
sftpService.logout();
}
} catch (SftpModuleException e) {
Assert.fail();
}
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testLogout() throws Exception {
try {
sftpService.login();
Assert.assertTrue(sftpService.logout());
} catch (SftpModuleException e) {
Assert.fail();
}
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testLogoutFailed() throws Exception {
try {
Assert.assertFalse(sftpService.logout());
} catch (SftpModuleException e) {
Assert.fail();
}
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testUploadFile() throws Exception {
try {
sftpService.login();
if(sftpService.isLogged()) {
sftpService.uploadFile(fileName, localPath, remotePath);
Assert.assertTrue(sftpService.existsFile(fileName, remotePath));
sftpService.logout();
}else{
Assert.fail();
}
} catch (SftpModuleException e) {
Assert.fail();
}
}
}
application.yml
/**
* Unit test for mailing service
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"../../core/config/canigo-core.xml"})
public class SftpServiceReleaseTest{
// File exchange
private String fileName = null;
private String localPath = null;
private String remotePath = null;
private String tempName = null;
@Autowired
@Qualifier("sftpService")
SftpService sftpService;
@Before
public void setUp() throws AddressException{
fileName = "application.yml";
remotePath = "/home/continuum/tmp";
localPath = this.getClass().getResource("/temp/arxiu_pujar.pdf").getPath();
localPath = localPath.substring(0, (localPath.lastIndexOf("/")+1));
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testLogin() throws Exception {
try {
Assert.assertTrue(sftpService.login());
if (sftpService.isLogged()) {
sftpService.logout();
}
} catch (SftpModuleException e) {
Assert.fail();
}
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testLogout() throws Exception {
try {
sftpService.login();
Assert.assertTrue(sftpService.logout());
} catch (SftpModuleException e) {
Assert.fail();
}
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testLogoutFailed() throws Exception {
try {
Assert.assertFalse(sftpService.logout());
} catch (SftpModuleException e) {
Assert.fail();
}
}
/**
* Documentació.
*
* @throws Exception Documentació
*/
@Test
public void testUploadFile() throws Exception {
try {
sftpService.login();
if(sftpService.isLogged()) {
sftpService.uploadFile(fileName, localPath, remotePath);
Assert.assertTrue(sftpService.existsFile(fileName, remotePath));
sftpService.logout();
}else{
Assert.fail();
}
} catch (SftpModuleException e) {
Assert.fail();
}
}
}