본문 바로가기
Develop Study/Spring 2025. 12. 29.

(25.12.29) Java Spring 에서의 SFTP

API를 통해서 서버간 데이터를 주고 받을 수 있지만, 비동기 방식으로 외부 서버와 서비스가 연동이 되어있을 때는,

파일 전송을 위해 SFTP 를 구성하기 된다

 

실제 업무에서도 SFTP를 통해 외부 서버와 파일을 주고 받으면서 서비스가 운용되므로 해당 부분을 JavaSpring 서버단 기준으로 정리하고, 또, 레거시인 기술과 다른 Integrated 된 모듈을 활용하는 방법으로도 정리하고자 한다.


SFTP

  • SSH File Transfer Protocol, 네트워크 프로토콜로, 원격 시스템 간에 파일을 안전하게 전송하고 관리하기 위한 규칙(통신 규약)
  • SSH 연결을 통해 파일 전송, 디렉토리 조작, 목록 열람 같은 작업을 처리

특징

  • 네트워크 트래픽 전부 암호화 (평문 전송 없음)
  • 기본적으로 SSH(포트 22) 위에서 동작
  • 인증(비밀번호/키) + 전송 모두 암호화된 채널로 수행
  • 업로드/다운로드뿐 아니라 디렉토리 조작도 가능
  • 서버 간 자동 전송에도 적합하여 자동화/스크립트 친화적

SFTP 서버

  • SFTP 서버 = SFTP 프로토콜을 지원하는 서비스
  • 단독 소프트웨어 / 서버 운영체제의 SSH 서버가 SFTP 기능을 포함
    • 접속할 수 있게 포트(보통 22)를 열어놓고 SSH/SFTP 요청을 받아 파일 업로드/다운로드를 처리하는 서버
    • NAS 역시 해당 기능을 가질 경우 SFTP 서버가 가능

JSch 를 사용한 SFTP

  • Java Secure Channel, Java에서 SSH 프로토콜을 사용하기 위한 라이브러리
  • Java 코드로 SSH 서버에 접속하고, 그 위에서 SFTP 같은 작업을 자동으로 수행가능
  • SSH 자체를 구현하는 게 아니라 SSH 프로토콜을 Java에서 사용할 수 있게 감싸놓은 도구

단점

  • JSch는 2000년대 초반에 만들어진 라이브러리
  • 공식 릴리즈가 수년간 사실상 멈춰 있음
  • 보안 패치 / 알고리즘 추가가 매우 느림
    • Algorithm negotiation fail
  • 모든 관리가 수동이기 때문에, connect close 관리가 없다면, 세션 누수, 커넥션 고갈, 좀비 세션이 존재 가능성
  • Session ChannelSftp 객체 재사용 불가 → 멀티쓰레드시 장애 발생
    • Batch , 스케쥴러 환경에 치명적

Example

public class SFTPUtil {

    private String host;
    private int port;
    private String user;
    private String passwd;
    private int connectTimeout = 3000;

    public SFTPUtil(String host, int port, String user, String passwd) {
        this.host = host; 
        this.port = port;
        this.user = user;
        this.passwd = passwd;
    }

    public boolean send(File sourceFile, String directory) {
        boolean result = false;

        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        FileInputStream fis = null;

        JSch jsch = new JSch();

        try {
            session = jsch.getSession(this.user, this.host, this.port);
            session.setPassword(this.passwd);
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect(this.connectTimeout);

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

            if (!existsDir(channelSftp, directory)) {
                makeDir(channelSftp, directory);
            }

            fis = new FileInputStream(sourceFile);
            String uploadFileName = Paths.get(directory, sourceFile.getName()).toString();

            channelSftp.cd(directory);
            channelSftp.put(fis, sourceFile.getName());

            result = true;

        } 
        
        ...

		private boolean existsDir(ChannelSftp channelSftp, String directory) {
    boolean exists = false;
    try {
        SftpATTRS attrs = channelSftp.stat(directory);
        exists = (attrs != null);
    } catch (Exception e) {

    }
    return exists;
}

private void makeDir(ChannelSftp channelSftp, String directory) {
    SftpATTRS attrs = null;
    String pathInfo = File.separator;
    String FS = File.separatorChar == '\\\\' ? "\\\\\\\\" : File.separator;
    String[] dirs = directory.split(FS);
    int length = dirs.length;

    String dir;
    StringBuffer sb = new StringBuffer(pathInfo);
    for (int i = 0; i < length; i++) {
        if (dirs[i] != null && !dirs[i].isEmpty()) {
            attrs = null;
            sb.append(dirs[i]).append(File.separator);
            dir = sb.toString();

            try {
                attrs = channelSftp.stat(dir);
            } catch (Exception e) {

            }

            if (attrs == null) {
                try {
                    channelSftp.mkdir(dir);
                    channelSftp.cd(dir);
                } catch (SftpException e) {

                }
            }
        }
    }
    
    ...
}

리소스 객체

  • Session : SSH 연결 (TCP + 인증)
  • Channel : SSH 위의 논리 채널
  • ChannelSftp : SFTP 전용 채널
  • FileInputStream : 로컬 파일 스트림

→ SFTP = SSH Session + SFTP Channel

SSH 세션 생성 & 연결 FLOW

JSch jsch = new JSch();

session = jsch.getSession(this.user, this.host, this.port);
session.setPassword(this.passwd);
session.setConfig("StrictHostKeyChecking", "no");
session.connect(this.connectTimeout);

  1. JSch 클라이언트 생성
  2. SSH 세션 객체 생성
  3. 비밀번호 인증 방식 설정
  4. 서버 호스트 키 검증 비활성화
  5. 지정된 시간 내 SSH 연결 시도

SFTP 채널 열기

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

  • SSH 연결 위에 SFTP 전용 채널 생성
    • **ls cd put get**같은 파일 시스템 명령 가능
    → 따라서 makeDir 에서도 서버 OS 파일 시스템과 1:1 대응해서 stat() cd() 을 사용해서 디렉토리 생성 작업을 가능하게 함

서버 디렉토리 확인 후 파일 업로드

if (!existsDir(channelSftp, directory)) {
    makeDir(channelSftp, directory);
}

fis = new FileInputStream(sourceFile);

channelSftp.cd(directory);
channelSftp.put(fis, sourceFile.getName());

  • 디렉토리가 없다면 새로 만듬
  1. 로컬 파일을 스트림으로 열고
  2. SFTP 서버 디렉토리로 이동
  3. 스트림 내용을 서버로 전송 → 종료

Spring Integration SFTP를 활용한 SFTP

  • Spring에서 제공하는 SFTP 전용 모듈
  • JSch 가 아닌 Apache MINA SSHD 내부 구현을 통한 SFTP

특징

  • 커넥션 관리 자동
  • 예외 처리 정돈됨
  • Spring Bean 기반 설정
  • 배치 / 파일 전송 / 연계 작업에 최적

→ JSch로 직접 하던 걸 Spring 스타일로 감싸준 것

Factory

@Bean
public DefaultSftpSessionFactory sftpSessionFactory() {
    DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory();
    factory.setHost("localhost");
    factory.setPort(22);
    factory.setUser("user");
    factory.setPassword("password");
    factory.setAllowUnknownKeys(true);
    return factory;
}

JSch의

  • JSch
  • Session
  • setConfig("StrictHostKeyChecking", "no")
  • 전부 대체

Fileupload

public class SpringSftpUploader {

    private final DefaultSftpSessionFactory sessionFactory;

    public SpringSftpUploader(DefaultSftpSessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public boolean send(File sourceFile, String remoteDir) {

        try (SftpSession session = sessionFactory.getSession();
             FileInputStream fis = new FileInputStream(sourceFile)) {

            if (!session.exists(remoteDir)) {
                session.mkdir(remoteDir);
            }

            String remotePath = remoteDir + "/" + sourceFile.getName();
            session.write(fis, remotePath);

            return true;

        } catch (Exception e) {
            return false;
        }
    }
}

  • SftpSession session = sessionFactory.getSession() 로 JSch 의 연결/인증/채털관리 모두 추상화되어 관리가 가능자
    • 디렉토리가 없을경우 검증이 필요없이 자동으로 만들어줌

참고자료

https://aws.amazon.com/ko/what-is/sftp/

 

Secure File Transfer Protocol이란 무엇인가요? SFTP - 설명됨 - AWS

SFTP가 무엇인지, 기업에서 SFTP를 사용하는 방식과 이유, AWS에서 SFTP를 사용하는 방법에 대해 알아보세요.

aws.amazon.com

 

https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol

 

 


물론 멀티스레드/싱글스레드 환경인지, 고도화 작업에 많은 리소스가 필요한지 점검을 해야겠지만, 여러 방법을 통해,

자동으로 스케쥴러와 연동해 주기적으로 데이터를 옮기거나 전달할때 유용하게 사용할 수 있을 것이다.