[Java] SFTP 사용을 위한 JSCH 메소드 예시

728x90

 


 

오늘은 Java/Spring 환경에서 사용하는 실무에서 SFTP/SSH 프로토콜을 사용하기 위해서 용하는 라이브러리 인 'Jsch' 사용법을 알아보려고 한다

 


위 포스팅을 목적은 내가 나중에 까먹었을 떄 위 포스팅을 보고 필요한 부분을 바로바로 복붙해 가기 위함이다.

그러므로 내가 자주 썻던 메소드 및 사용예시에 대해서 만 설명해볼 예정이다..

 

1. JSCH 란?

http://www.jcraft.com/jsch/ 위 사이트는 공식문서 이다.

공식문서만 딱 들어가봐도 꽤나 오래되 보이는 사이트가 있고 그만큼 오래된 전통 라이브러리로 판정이 된다

2018년 6월이 마지막 업데이트로 생각보다 오래된(?) 라이브러리 이다

기술이 노후되고 관리되지 않았다고 안좋고 나쁜기술은 아니라고 생각한다

무료로 쓰는 입장에서, 이정도면 충분히 훌륭하다고 생각한다

대충 위 'JCraft' 는 SFTP/SSH 관련 라이브러리 정도다 라는 것만 알고있으면 될것으로 생각한다

 

그 중에서 나는 'SFTP' 관련 내용만 다뤄보려고 한다.

 

오픈 소스 개발자분들은 항상 감사하고 존경하는 마음을 가지고 있다

바로 이제 사용을 해보자. 아래 Gradle 을 복붙해서 build.gradle 에 추가하자

본인 환경은 참고로 'Java21, SpringBoot3.4 를 사용하는 환경이다'

implementation group: 'com.jcraft', name: 'jsch', version: '0.1.55'

 

자주 사용하는 로직 설명에 앞서 자주 사용하는 메소드를 설명해보겠다.

 

1-1) JSCH 주요 메소드

 

아래 코드들은 이미 SFTP 세션이 생성되어 open 되어 있는 상태에서만 사용이 가능합니다.

 

import java.io.InputStream;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpException;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JschExample {

	public void Example() throws SftpException {
		ChannelSftp channelSftp = new ChannelSftp();

		/**
		 * @Param : 이동할 경로를 적는다
		 * '/home/hkjin' 경로로 이동한다.
		 * */
		channelSftp.cd("/home/hkjin");

		// 현재 경로를 체크한다.
		String pwd = channelSftp.pwd();
		log.info("현재 경로는 : {}", pwd);

		/**
		 * @Return : 보통 파일의 메모리에 담아서 작업을 하기에 반환값은 InputStream 이다.
		 * @Param : 읽어올 파일 이름을 적는다.
		 * 현재 경로에서 'abc.txt' 파일을 가져온다.
		 * */
		InputStream inputStream = channelSftp.get("abc.txt");


		/**
		 * @Param1 : InputStream 에 담긴 byte 파일
		 * @Param2 : 파일을 넣고 싶은 경로
		 * @Param3 : [Optional] 조건이 있으면 넣는다 ex) OVERWRITE(덮어쓰기)
		 * 파일을 원하는 경로에 넣는다 -> 같은 파일이 있으면 덮어쓰기 진행한다. .
		 * */
		channelSftp.put(inputStream, path, ChannelSftp.OVERWRITE);


		/**
		 * @Param : 폴더를 생성할 경로
		 * 원하는 경로에 폴더를 생성한다.
		 * */
		channelSftp.mkdir("/home/hkjin/java");


		/**
		 * @Param : 권한 부여
		 * @Param : 권한 부여할 폴더 경로
		 * 위 폴더에 권한을 준다.
		 * */
		channelSftp.chmod(0_660, "/home/hkjin/java");


		/**
		 * @Return : String
		 * 서버의 '루트' 디렉토리로 돌아간다.
		 * */
		String home = channelSftp.getHome();

		/**
		 * @Param : 받아온 경로에 무슨 파일이 있나 체크한다.
		 * 위 경로에 어떠한 파일이 있나 체크한다.
		 * */
		channelSftp.ls("/home/hkjin/java");
		
		
		/**
		 * sftp 세션을 종료한다.
		 * */
		channelSftp.disconnect();

	}
}

 

 

위 메소드의 쓰임새 및 사용은 주석으로 간단하게 설명을 적어 두었습니다.

 

 

2. JSCH 사용 예시

기본적으로 SFTP 를 사용하기 위해 Config 가 필요하다

@Getter
@Setter
@Component
@ConfigurationProperties(ignoreUnknownFields = false, prefix = "sftp.client")
@ToString
public class SftpProperties {
    private String host;
    private Integer port;
    private String protocol;
    private String username;
    private String password;
    private String root;
    private String privateKey;
    private String passphrase;
    private String sessionStrictHostKeyChecking;
    private Integer sessionConnectTimeout;
    private Integer channelConnectedTimeout;
}

 

위 파일에 해당하는 값들은 application.yml 에 정의를 해둔 다음에 사용한다.

 

sftp:
  client:
    protocol: sftp
    port: 22
    host: '127.0.0.1' # // 연결할 파일 서버
    username: hkjin // 파일 서버 접속 계정
    password: 1234 // 파일 서버 접속 pw
    root: / // 접속 후 경로
    sessionStrictHostKeyChecking: no
    sessionConnectTimeout: 15000 
    channelConnectedTimeout: 15000

 

 

 

ㅇㅇㄴ

 

 

2-1) SFTP 세션 생성

private final SftpProperties config;

public ChannelSftp createSftp() throws Exception {
    JSch jsch = new JSch();

    Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
    session.setPassword(config.getPassword());
    session.connect(config.getSessionConnectTimeout());

    Channel channel = session.openChannel(config.getProtocol());
    channel.connect(config.getChannelConnectedTimeout());

    log.info("SFTP Channel 생성 완료 {}.", config.getHost());

    return (ChannelSftp)channel;
}

 

말 그대로 SFTP 에 접속할 수 있는 세션을 생성한다.

 

2-2) 세션 끊기

    public void disconnect(Channel sftp) {
        try {
            if (sftp != null) {
                if (sftp.isConnected()) {
                    sftp.disconnect();
                }
                if (sftp.isClosed()) {
                    log.info("[FileService] closed connection already");
                }
                if (null != sftp.getSession()) {
                    sftp.getSession().disconnect();
                }
            }
        } catch (JSchException e) {
            e.printStackTrace();
        }
    }

 

Java 에서 I/O 작업을 마친후에는 close() 를 하는 관습이 있다

위 또한 같다. sftp 세션 생성 후 일이 마무리 되었으면 sftp 세션 커넥션을 끊어야 한다

끊음으로써 불필요한 세션을 서버가 가지고 있지 않으므로 리소스 관리에 더 용이해진다

 

 

2-3) 폴더 생성

실무에서 폴더 생성은 서버 계정 별 권한이 있기에 잘 알고서 할 필요가 있다

    public boolean createDirs(String dirPath, Integer permission, ChannelSftp sftp) {
        if (dirPath != null && !dirPath.isEmpty()
                && sftp != null) {
            String[] dirs = Arrays.stream(dirPath.split("/"))
                    .filter(StringUtils::isNotBlank)
                    .toArray(String[]::new);

            for (String dir : dirs) {
                try {
                    sftp.cd(dir);
                    log.info("Change directory {}", dir);
                } catch (Exception e) {
                    try {
                        sftp.mkdir(dir);
                        if (permission != null)
                            sftp.chmod(permission, dir);
                        log.info("Create directory {}", dir);
                    } catch (SftpException e1) {
                        log.error("Create directory fail : {}", dir, e1);
                        e1.printStackTrace();
                    }
                    try {
                        sftp.cd(dir);
                        log.info("Change directory {}", dir);
                    } catch (SftpException e1) {
                        log.error("Change directory fail : {}", dir, e1);
                        e1.printStackTrace();
                    }
                }
            }
            return true;
        }
        return false;
    }

 

기본적으로 나는 파라미터를 3개를 받아서 사용한다.

  • dirPath : 생성할 폴더 경로
  • permission : 폴더 부여 권한
  • sftp : sftp 세션

 

2-4) 파일 업로드

나는 파일 메모리에 담아서 내가 원하는 폴더 경로에 담는 방향으로 로직을 많이 구상했다

    public boolean uploadFile(String targetPath, InputStream inputStream) throws Exception {
        ChannelSftp sftp = this.createSftp();
        try {
            sftp.cd(config.getRoot());
            log.info("Change path to {}", config.getRoot());

            int index = targetPath.lastIndexOf("/");
            String fileDir = targetPath.substring(0, index);
            String fileName = targetPath.substring(index + 1);
            boolean dirs = this.createDirs(fileDir, sftp);
            if (!dirs) {
                log.error("Remote path error. path:{}", targetPath);
                throw new Exception("Upload File failure");
            }
            sftp.put(inputStream, fileName);
            return true;
        } catch (Exception e) {
            log.error("Upload file failure. TargetPath: {}", targetPath, e);
            throw new Exception("Upload File failure");
        } finally {
            this.disconnect(sftp);
        }
    }

 

보통 파일을 업로드 할 때 폴더를 생성 후 내가 원하는 폴더 하위에 파일을 생성하고는 한다

그래서 createDir() 과 uploadFile() 은 같이 사용될 때가 많다

 


+ 2025.01.20 추가적인 메소드가 있을 때 계속 해서 수정해서 업데이트를 할 예정임

 

 

 

결론

새로운 라이브러리를 사용해보는 것은 늘 새롭고 재밌다.

누군가 다른 사람들의 편의를 위해 본인의 노력의 결과물을 무료로 배푸는 것 또한 너무 멋있다고 생각한다.

 

나는 오픈소스 자체를 만들 실력은 안되므로..노력을 해서 누군가 만들어둔 노력에 조금이라도 보탬이 되는

오픈 소스 기여자가 되기 위해 노력을 해보고 싶다.

 

 

728x90