hilims 님의 블로그
Spring Boot에서 SFTP 파일 업로드 & 다운로드 구현 (JSch 기반) 본문
개요
Spring Boot 프로젝트에서 SFTP 서버와 파일을 주고받기 위한 서비스 로직을 구현했습니다.
이를 통해 손쉽게 파일을 업로드, 다운로드, 삭제할 수 있으며, 연결 관리 및 디렉토리 자동 생성 기능도 포함되어 있습니다.
⚙️ 의존성 추가
build.gradle 또는 pom.xml에 아래 의존성을 추가합니다.
implementation 'com.jcraft:jsch:0.1.55'
🧱 설정 파일 (application.yml)
sftp:
upload:
connection:
host: sftp.server.address
credentials:
user: sftpUser
password: sftpPassword
💡 주요 기능
- ✅ SFTP 연결 및 해제 관리
- ✅ 디렉토리 자동 생성 (
mkdir -p역할) - ✅ 파일 업로드 / 다운로드 / 삭제
- ✅ 파일 존재 여부 확인
- ✅ Spring
MultipartFile직접 업로드 지원
🧑💻 SftpService.java
package com.swinnus.reefer.service.common.sftp;
import com.jcraft.jsch.*;
import com.swinnus.reefer.dto.common.FileVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Objects;
@Slf4j
@Service
@RequiredArgsConstructor
public class SftpService {
@Value("${sftp.upload.connection.host}")
private String host;
@Value("${sftp.upload.credentials.user}")
private String user;
@Value("${sftp.upload.credentials.password}")
private String password;
private final int port = 22;
private final JSch jsch = new JSch();
/** SFTP 연결 */
private ChannelSftp openSftp() {
try {
Session session = jsch.getSession(user, host, port);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");
sftp.connect();
sftp.setFilenameEncoding("UTF-8");
log.debug("SFTP connected: {}@{}", user, host);
return sftp;
} catch (JSchException e) {
throw new RuntimeException("SFTP connection failure: " + e.getMessage(), e);
} catch (SftpException e) {
throw new RuntimeException(e);
}
}
/** 연결 종료 */
private void closeSftp(ChannelSftp sftp) {
if (Objects.isNull(sftp)) return;
try {
Session session = sftp.getSession();
sftp.disconnect();
if (session != null && session.isConnected()) session.disconnect();
log.debug("SFTP disconnected: {}@{}", user, host);
} catch (Exception e) {
log.warn("SFTP Closing failure", e);
}
}
/** 디렉토리 자동 생성 */
private void createDirectories(ChannelSftp sftp, String remoteDir) throws SftpException {
String[] folders = remoteDir.replace("\\", "/").split("/");
String path = "";
for (String folder : folders) {
if (folder == null || folder.trim().isEmpty()) continue;
path += "/" + folder;
try {
sftp.cd(path);
} catch (SftpException e) {
sftp.mkdir(path);
sftp.cd(path);
}
}
}
/** 파일 다운로드 */
public byte[] downloadToBytes(String remotePath) {
ChannelSftp sftp = openSftp();
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
sftp.get(remotePath.replace("\\", "/"), baos);
return baos.toByteArray();
} catch (SftpException | java.io.IOException e) {
throw new RuntimeException("File download failure: " + remotePath, e);
} finally {
closeSftp(sftp);
}
}
/** 파일 존재 여부 확인 */
public boolean exists(String remotePath) {
ChannelSftp sftp = openSftp();
try {
sftp.stat(remotePath.replace("\\", "/"));
return true;
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) return false;
throw new RuntimeException("Failure to confirm whether the file exists: " + remotePath, e);
} finally {
closeSftp(sftp);
}
}
/** 파일 업로드 */
public FileVO uploadFile(MultipartFile file, String basePath, String directoryPath) {
ChannelSftp sftp = openSftp();
try (InputStream inputStream = file.getInputStream()) {
String remoteDir = (basePath + "/" + directoryPath).replace("\\", "/");
createDirectories(sftp, remoteDir);
String remotePath = remoteDir + "/" + file.getOriginalFilename();
sftp.put(inputStream, remotePath);
return FileVO.builder()
.filePath(directoryPath.replace("\\", "/"))
.fileName(file.getOriginalFilename())
.fileSize(file.getSize())
.storagePath(remotePath)
.build();
} catch (Exception e) {
throw new RuntimeException("File upload failure: " + file.getOriginalFilename(), e);
} finally {
closeSftp(sftp);
}
}
/** 파일 삭제 */
public void deleteFile(String remotePath) {
ChannelSftp sftp = openSftp();
try {
sftp.rm(remotePath.replace("\\", "/"));
} catch (SftpException e) {
throw new RuntimeException("File deletion failure: " + remotePath, e);
} finally {
closeSftp(sftp);
}
}
}
📂 FileVO 예시
@Builder
public record FileVO(
String filePath,
String fileName,
long fileSize,
String storagePath
) {}
🧠 포인트 정리
| 기능 | 설명 |
|---|---|
openSftp() |
SFTP 연결 생성 |
closeSftp() |
연결 및 세션 종료 |
createDirectories() |
상위 디렉토리 자동 생성 |
uploadFile() |
MultipartFile 업로드 및 경로 리턴 |
downloadToBytes() |
파일을 byte[] 형태로 다운로드 |
exists() |
파일 존재 여부 확인 |
deleteFile() |
파일 삭제 처리 |
🧾 예외 처리 팁
JSchException: Auth fail→ 사용자명 / 비밀번호 / 포트 확인Connection timed out→ 방화벽 혹은 네트워크 설정 문제SSH_FX_NO_SUCH_FILE→ 경로 오타 또는 권한 문제