com.jcraft.jsch 라이브러리를 이용한 java sftp 파일 업로드
SFTP(Secure File Transfer Protocol)는 SSH(Secure Shell) 기반의 보안 파일 전송 프로토콜인데요.
일반 파일 전송 프로토콜인 FTP(File Transfer Protocol)와는 다르게 파일 전송 시 모든 정보를 암호화하여 통신하며, 때문에 보안상의 문제를 방지하고 안전하게 파일을 전송하고 받을 수 있습니다.
(연결이 안전하다는 장점이 있지만 때문에 SSH 키의 유효성 검사 및 관리가 추가적으로 필요합니다.)
java에서는 sftp 파일 전송을 위해 JSch 라이브러리가 많이 사용되는데요.
해당 포스팅은 com.jcraft.jsch 라이브러리를 사용한 sftp 접속 및 파일 업로드 구현 예시입니다.
(포스팅 맨 하단에 github 주소도 함께 해놓았으니 필요하신 경우 실제 코드도 참고하시면 좋을 것 같습니다.)
***
아래 예시는 패스워드 방식이 아닌 공개 키, 개인 키를 생성하고 공개 키를 sftp 서버에 등록, 접속하는 클라이언트에서는 개인 키를 가지고 인증하는 방식이 사용되는데요.
포스팅에서 ssh 인증 키 생성 및 등록에 대한 내용은 생략되어 있으며, 해당 부분에 대해 잘 모르시는 경우 'ssh key 생성', 'ssh 공개키 인증' 등의 키워드로 구글링 하여 참고하시면 좋을 것 같습니다.
@Slf4j
@Component
public class SftpUtils {
@Value("${ttotw.sftp.server}")
private String server;
@Value("${ttotw.sftp.port}")
private int port;
@Value("${ttotw.sftp.username}")
private String username;
@Value(("${ttotw.sftp.privateKey}"))
private String privateKey;
@Value("${ttotw.sftp.documentRoot}")
private String fileServerDocumentRoot;
private JSch jSch;
private Session jSchSession;
private Channel channel;
private ChannelSftp channelSftp;
//sftp 서버 연결
private void open() { ... }
//sftp 서버 연결 종료
private void close() { ... }
//파일 업로드
public void upload(MultipartFile file) { ... }
}
SFTP 파일 업로드를 위한 SftpUtils 클래스입니다. @Component 어노테이션을 사용하여 해당 클래스를 빈 등록하고, 필요한 곳에서 가져다 쓸 수 있도록 하였습니다.
(예시와 같이 빈으로 등록하여 사용하는 방법 말고도 생성자를 통해 객체를 생성하여 사용할 수도 있으며, 이 부분은 사용되는 환경에 맞게 적용하면 될 것 같습니다.)
sftp 서버 연결과 종료 과정은 파일 업로드, 다운로드, 삭제 등에 공통적으로 필요한 부분이기 때문에 각각 open(), close() 메서드로 빼서 처리할 수 있게 구현하였는데요. 아래 내용을 통해 각각의 메서드에 대해 자세하게 살펴보겠습니다.
open() method
private void open() {
//JSch 객체를 생성
jSch = new JSch();
try {
//privateKey 인증
jSch.addIdentity(privateKey);
//JSchSession 객체를 생성 (사용자 이름, 접속할 호스트, 포트 전달)
jSchSession = jSch.getSession(username, server, port);
//기타 설정 적용
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
jSchSession.setConfig(config);
//접속
jSchSession.connect();
//sftp 채널 열기 및 접속
channel = jSchSession.openChannel("sftp");
channel.connect();
} catch (JSchException e) {
e.printStackTrace();
log.error("SFTP:: server connect failed.");
}
//채널을 FTP 용 채널 객체로 캐스팅
channelSftp = (ChannelSftp) channel;
}
위에서 이야기한 것처럼 예시에서 사용된 방식은 비밀번호 인증을 통한 접속이 아닌 ssh 인증을 통한 접속에 대한 예시인데요.
JSch 객체 생성 후 addIdentity() 메서드를 통해 ssh 인증 정보를 추가하게 되며, addIdentity() 메서드의 파라미터에는 클라이언트가 가지고 있는 개인 키의 전체 경로(파일 명을 포함한)가 들어가게 됩니다.
//privateKey 인증(Passphrase를 사용하는 경우)
jSch.addIdentity(privateKey, passphrase);
만약 ssh 인증 키 생성 시 Passphrase를 추가하여 생성한 경우, 인증 부분에서 다음과 같이 passphrase를 추가해 주면 됩니다.
close() method
private void close() {
if (jSchSession.isConnected()) {
channelSftp.disconnect();
jSchSession.disconnect();
}
}
다음으로 sftp 서버에서 필요한 작업을 끝낸 후 연결을 종료하는 close() method입니다.
ChannelSftp 클래스에는 disconnect() 메서드 외에도 quit(), exit()라는 비슷한 기능을 할 것 같은 메서드들이 있는데요.
quit(), exit() 메서드는 내부적으로 disconnect() 메서드를 호출하고 있고, disconnect() 메서드는 super.disconnect()를 통해 상위 추상 클래스인 Channel 클래스의 disconnect() 메서드를 호출하게 됩니다.
그리고 채널의 연결 종료는 channelSftp 인스턴스와 channel 인스턴스에 대해 각각 연결을 끊을 필요 없이 channelSftp 인스턴스만 disconnect 해주면 됩니다.
(이 부분은 channelSftp 인스턴스의 연결을 끊은 뒤 isConnected() 메서드를 통해 확인해 볼 수 있습니다.)
upload() method
public void upload(MultipartFile file) {
open();
try (InputStream inputStream = file.getInputStream()){
//file extension
String fileExtension = StringUtils.getFilenameExtension(file.getOriginalFilename());
channelSftp.put(inputStream, fileServerDocumentRoot + "/" + file.getOriginalFilename());
} catch (IOException | SftpException e) {
log.error("SFTP:: file upload failed.");
e.printStackTrace();
} finally {
close();
}
}
실제로 파일을 업로드하는 upload() 메서드입니다. 위에서 구현된 open(), close() 메서드를 통해 서버와 연결하고, 동작이 종료된 후 연결을 종료하는 것을 볼 수 있습니다.
해당 메서드에서 실질적인 파일 업로드가 실행되는 부분은 channelSftp 인스턴스의 put() 메서드가 사용되는 부분인데요.
예시에서는 첫 번째 파라미터로 file의 inputStream가 들어가고, 두 번째 파라미터로 파일 명 및 확장자를 포함한 전체 업로드 경로가 들어가게 됩니다.
catch 부분에서 IOException과 SftpException을 한 번에 처리하도록 해놨는데, 이 부분의 경우 필요에 따라 각각의 exception을 따로 처리할 수 있습니다.
또한 해당 코드에서는 inputStream을 사용하는 부분에서 try-with-resources 방식을 사용했는데요.
try-finally 방식으로 처리하게 된다면 아래와 같은 코드가 됩니다.
(try-finally 방식에 비해 try-with-resources 방식의 장점이 많기 때문에 위 코드를 사용하는 것을 권장합니다.)
public void upload(MultipartFile file) {
open();
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
//file extension
String fileExtension = StringUtils.getFilenameExtension(file.getOriginalFilename());
channelSftp.put(inputStream, fileServerDocumentRoot + "/" + file.getOriginalFilename());
} catch (IOException | SftpException e) {
log.error("SFTP:: file upload failed.");
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
log.error("SFTP:: file upload failed.");
}
close();
}
}
(try-finally 방식)
여기까지 JSch 라이브러리를 사용한 java sftp 파일 업로드 구현 예시입니다.
sftp 연결과 종료 메서드의 경우 그대로 사용하여도 무방하지만, 파일 업로드 메서드(및 다운로드, 삭제 등) 같은 경우에는 실제 사용되는 부분에서 업로드될 폴더의 존재 유무 판단, 폴더 생성 등의 추가적인 기능이 필요한데요.
폴더 및 파일 존재 유무는 ChannelSftp 클래스의 ls() 메서드를 기반으로 생성할 수 있으며, 폴더 생성의 경우 mkdir() 메서드를 통해 실행할 수 있습니다.
(기본적인 구현 외에 폴더 존재 유무 확인 및 폴더 생성이 적용된 코드의 경우 아래 github를 참고하시면 좋을 것 같습니다.)
< sftp, ftp 관련 포스팅 >
2023.02.13 - [Programming/Java] - Java FTPClient를 통한 ftp 파일 업로드 내용 정리
2023.02.06 - [Programming/Java] - Java 파일 확장자 구하는 방법 (filename extension)
< github 주소 >
< 참고 자료 >
'Programming > Java' 카테고리의 다른 글
ConcurrentHashMap 개념과 동기화 동작 원리(Thread-safe) (0) | 2023.05.21 |
---|---|
(java) 어플리케이션 단계에서 외부 이미지 파일 불러오는 방법 (0) | 2023.03.01 |
Java FTPClient를 통한 ftp 파일 업로드 내용 정리 (0) | 2023.02.13 |
Java 파일 확장자 구하는 방법 (filename extension) (0) | 2023.02.06 |
FTP로 살펴보는 SocketTimeout(soTimeout)과 ConnectionTimeout 차이점 (0) | 2023.02.01 |