【JAVA 访问SFTP的方法 sshj/Jsch】

本文详细介绍了如何使用Java的JSch和SSHJ库进行SFTP访问,包括SSH连接中的加密组件如KEX、Ciphers和MACs,以及两种库的Maven依赖和示例代码,特别提到了JSch对SHA2的支持不足,建议考虑使用支持SHA2的SSHJ库,如ApacheMINASSHD。

SSH 链接的加密组件

这里大概介绍下都有些什么东西

KEX (密钥交换) 或者叫 Key exchange

KEX算法用于在SSH连接建立时,在客户端和服务器之间安全地交换密钥。这些密钥随后用于加密和解密传输的数据。

Ciphers (密码)

Ciphers是用于加密和解密数据的算法。在SSH连接中,Ciphers确保传输的数据在客户端和服务器之间是加密的,从而防止未经授权的访问

MACs (消息认证码)

MACs用于验证数据的完整性和真实性。它们确保数据在传输过程中没有被篡改,并且确实来自预期的发送者。

1. Jsch

Jsch官网文档地址: http://www.jcraft.com/jsch/

这个工具是比较好用的,但是!但是当前最新版本 MACs 不支持 sha2,以后应该会更新的吧。

干货

包含用户名密码方式链接 和 用私钥方式链接

Maven依赖

		<dependency>
			<groupId>com.jcraft</groupId>
			<artifactId>jsch</artifactId>
			<version>0.1.55</version>
		</dependency>

工具类

import com.jcraft.jsch.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.util.IOUtils;

import java.io.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

public class JschUtil {

    private static Log log = LogFactory.getLog(JschUtil.class);

    private ChannelSftp sftp;

    private Session session;

     //SFTP 登录用户名
     private String username;
     //SFTP 登录密码
     private String password;
     // 私钥
     private String privateKey;
     //SFTP 服务器地址IP地址
     private String host;
     //SFTP 端口
     private int port;

    /**
     * 构造基于密码认证的sftp对象
     */
    public JschUtil(String username, String password, String host, int port) {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = port;
    }

    /**
     * 构造基于秘钥认证的sftp对象
     */
    public JschUtil(String username, String host, int port, String privateKey) {
        this.username = username;
        this.host = host;
        this.port = port;
        this.privateKey = privateKey;
    }

    public JschUtil() {

    }


    /**
     * 连接sftp服务器
     */
    public Boolean login() {
        try {
            JSch jsch = new JSch();
            if (privateKey != null) {
                // 设置私钥
                String path = this.getClass().getClassLoader().getResource("").getPath();
                path += privateKey;
                jsch.addIdentity(path);
            }
            session = jsch.getSession(username, host, port);
            if (password != null) {
                session.setPassword(password);
            }

            Properties config = new Properties();
            // 设 No JSch将不会检查远程主机的主机密钥是否与本地已知主机密钥数据库中存储的密钥匹配
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.connect();
            Channel channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 关闭连接 server
     */
    public void logout() {
        if (sftp != null) {
            if (sftp.isConnected()) {
                sftp.disconnect();
            }
        }
        if (session != null) {
            if (session.isConnected()) {
                session.disconnect();
            }
        }
    }

    /**
     * 将输入流的数据上传到sftp作为文件。文件完整路径=basePath+directory
     *
     * @param directory    上传到该目录
     * @param sftpFileName sftp端文件名
     */
    public boolean upload(String directory, String sftpFileName, InputStream input) throws SftpException {
        try {
            if (directory != null && !"".equals(directory)) {
                sftp.cd(directory);
            }
            sftp.put(input, sftpFileName);  //上传文件
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public void cd(String directory) throws SftpException {
        if (directory != null && !"".equals(directory) && !"/".equals(directory)) {
            sftp.cd(directory);
        }
    }


    /**
     * 下载文件
     * @param directory 下载目录
     * @param downloadFile 下载的文件
     * @param saveFile 存在本地的路径
     */
    public void download(String directory, String downloadFile, String saveFile) {
        System.out.println("download:" + directory + " downloadFile:" + downloadFile + " saveFile:" + saveFile);
        File file = null;
        try {
            if (directory != null && !"".equals(directory)) {
                sftp.cd(directory);
            }
            file = new File(saveFile);
            sftp.get(downloadFile, new FileOutputStream(file));
        } catch (SftpException e) {
            e.printStackTrace();
            if (file != null) {
                file.delete();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            if (file != null) {
                file.delete();
            }
        }
    }


    /**
     * 下载文件
     * @param directory 下载目录
     * @param downloadFile 下载的文件名
     * @return 字节数组
     * @throws SftpException
     * @throws IOException
     */
    public byte[] download(String directory, String downloadFile) throws SftpException, IOException {
        if (directory != null && !"".equals(directory)) {
            sftp.cd(directory);
        }
        InputStream is = sftp.get(downloadFile);
        byte[] fileData = IOUtils.toByteArray(is);
        return fileData;
    }

    /**
     * 删除文件
     * @param directory 要删除文件所在目录
     * @param deleteFile 要删除的文件
     * @throws SftpException
     */
    public void delete(String directory, String deleteFile) throws SftpException {
        if (directory != null && !"".equals(directory)) {
            sftp.cd(directory);
        }
        sftp.rm(deleteFile);
    }

    /**
     * 列出目录下的文件
     * @param directory 要列出的目录
     * @return
     * @throws SftpException
     */
    public Vector<?> listFiles(String directory) throws SftpException {
        return sftp.ls(directory);
    }

    /**
     * 判断文件是否存在
     * @param directory
     * @param fileName
     * @return
     */
    public boolean isExistsFile(String directory, String fileName) {
        List<String> findFilelist = new ArrayList();
        ChannelSftp.LsEntrySelector selector = new ChannelSftp.LsEntrySelector() {
            @Override
            public int select(ChannelSftp.LsEntry lsEntry) {
                if (lsEntry.getFilename().equals(fileName)) {
                    findFilelist.add(fileName);
                }
                return 0;
            }
        };
        try {
            sftp.ls(directory, selector);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (findFilelist.size() > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * sftp判断目录是否存在
     * @param path
     * @return
     */
    public boolean isExistDir(String path){
        boolean  isExist=false;
        try {
            SftpATTRS sftpATTRS = sftp.lstat(path);
            isExist = true;
            return sftpATTRS.isDir();
        } catch (Exception e) {
            if (e.getMessage().toLowerCase().equals("no such file")) {
                isExist = false;
            }
        }
        return isExist;
    }
 }

2.SSHJ (net.schmizz.sshj)

SSHJ官网文档地址: https://github.com/hierynomus/sshj

SSHj 密码或者密钥访问

Maven

	<dependency>
	    <groupId>com.hierynomus</groupId>
	    <artifactId>sshj</artifactId>
	    <version>0.38.0</version>
	</dependency>

SSHJ工具类


import com.jcraft.jsch.SftpException;
import net.schmizz.sshj.Config;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.userauth.keyprovider.KeyProvider;
import net.schmizz.sshj.xfer.FileSystemFile;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class SFTPUtil {

    private static Log log = LogFactory.getLog(SFTPUtil.class);


     //SFTP 登录用户名
     private String username;
     //SFTP 登录密码
     private String password;
     // 私钥
     private String privateKey;
     //SFTP 服务器地址IP地址
     private String host;
     //SFTP 端口
     private int port;

    private SFTPClient sftpClient;

    /**
     * 构造基于密码认证的sftp对象
     */
    public SFTPUtil(String username, String password, String host, int port) {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = port;
    }

    /**
     * 构造基于秘钥认证的sftp对象
     */
    public SFTPUtil(String username, String host, int port, String privateKey) {
        this.username = username;
        this.host = host;
        this.port = port;
        this.privateKey = privateKey;
    }

    public SFTPUtil() {

    }

    /**
     * 连接sftp服务器
     */
    public Boolean login() {
         try {
 			// 没有特殊要求的忽略config 直接 SSHClient sshClient = new SSHClient();
             MyConfig myConfig = new MyConfig();
            
             // 设置CiphersFactories
             myConfig.setCiphersFactories("aes256-ctr,aes192-ctr,aes128-ctr,aes256-gcm@openssh.com,aes128-gcm@openssh.com");
             // 设置 MACsFactories
             myConfig.setMacFactories("hmac-sha2-512,hmac-sha2-256,hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com");
             // 设置 KEXFactories
             myConfig.setKexFactories("ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,curve25519-sha256@libssh.org");
             Config config = myConfig;
             SSHClient sshClient = new SSHClient(config);

             sshClient.addHostKeyVerifier(new PromiscuousVerifier());
             sshClient.connect(host,port);

             // 密钥链接
             if(StringUtils.isNotBlank(privateKey)){
                 // getPrivateKeyPath 只是个获取路径的方法 酌情修改
                 String path = getPrivateKeyPath(privateKey);
                 KeyProvider keys = sshClient.loadKeys(path);
                 sshClient.authPublickey(username, keys);
             }
             // 密码链接
             if(StringUtils.isNotBlank(password)){
                 sshClient.authPassword(username,password);
             }

             // 创建sftp链接
             sftpClient = sshClient.newSFTPClient();

             log.info("建立SFTP连接成功--------");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 关闭连接 server
     */
    public void logout() {
        try {
            sftpClient.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 将输入流的数据上传到sftp作为文件。文件完整路径=basePath+directory
     *
     * @param directory    上传到该目录
     * @param sftpFileName sftp端文件名
     */
    public boolean upload(String directory, String sftpFileName, File file) throws SftpException {
        try {
            sftpClient.put(new FileSystemFile(file),directory+"/" + sftpFileName);
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    /**
     * 下载文件
     * @param directory 下载目录
     * @param downloadFile 下载的文件
     * @param saveFile 存在本地的路径
     */
    public void download(String directory, String downloadFile, String saveFile) {
        log.info("download:" + directory + " downloadFile:" + downloadFile + " saveFile:" + saveFile);
        File file = null;
        try {
            String remoteFilePath = directory + "/" + downloadFile;
            file = new File(saveFile);
            sftpClient.get(remoteFilePath,new FileSystemFile(file));
        } catch (Exception e) {
            e.printStackTrace();
            if (file != null) {
                file.delete();
            }
        }
    }

    /**
     * 删除文件
     * @param directory 要删除文件所在目录
     * @param deleteFile 要删除的文件
     * @throws SftpException
     */
    public void delete(String directory, String deleteFile) throws SftpException {
        try {
            sftpClient.rm(directory+"/"+deleteFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 列出目录下的文件
     * @param directory 要列出的目录
     * @return
     * @throws SftpException
     */
    public List<RemoteResourceInfo> listFiles(String directory) throws SftpException {
        try {
            return sftpClient.ls(directory);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    public boolean isExistsFile(String directory, String fileName) {
        try {
            List<RemoteResourceInfo> list = sftpClient.ls(directory);

            for (RemoteResourceInfo info : list) {
                if(info.getName().equals(fileName)){
                    return true;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }


    public String getPrivateKeyPath(String privateKey){
        String path = this.getClass().getClassLoader().getResource("").getPath();
        return path += privateKey;
    }

    public static void main(String[] args) throws SftpException, IOException {
        String username = "root";
        String host = "127.0.0.1";// 目标地址
        String privateKey = "id_rsa";
        int port = 22;

        String directory = "/test";
        File file = new File("/test/test.txt");
        
        SFTPUtil sftp = new SFTPUtil(username,host,port,privateKey);

        boolean isLogin = sftp.login();
        if(isLogin){
            log.info("IP:"+ sftp.host +"SFTP login success");
        }else{
            log.info("IP:"+ sftp.host +"SFTP login fail");
            return;
        }

        if(file != null){
            boolean isUpload = sftp.upload(directory,file.getName(),file);
            if(isUpload){
                log.info("fileName:"+file.getName()+" , targetPath:"+ directory +" ; Success !!!");
            }else{
                log.info("fileName:"+file.getName()+" , targetPath:"+ directory +" ; Fail !!!");
            }
        }

        boolean isExist = sftp.isExistsFile(directory,file.getName());
        if(isExist){
            log.info("fileName:"+file.getName()+" , targetPath:"+ directory +" ;  File Exist to SFTP !!!");
        }else{
            log.info("fileName:"+file.getName()+" , targetPath:"+ directory +" ; File Not Exist to SFTP !!!");
        }

        sftp.logout();
    }
}

配置文件 MyConfig()

创建 加密方式工厂


import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.common.Factory;
import net.schmizz.sshj.common.LoggerFactory;
import net.schmizz.sshj.transport.cipher.Cipher;
import net.schmizz.sshj.transport.kex.KeyExchange;
import net.schmizz.sshj.transport.mac.MAC;
import org.slf4j.Logger;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MyConfig extends DefaultConfig {
    private Logger logger;

    public MyConfig(){
        super();
        setLoggerFactory(LoggerFactory.DEFAULT);
    }

    @Override
    public void setLoggerFactory(LoggerFactory loggerFactory) {
        super.setLoggerFactory(loggerFactory);
        logger = loggerFactory.getLogger(getClass());
    }

    public void setCiphersFactories(String input) {
        String[] ciphers = input.trim().split(",");
        List<String> factoryNames = Factory.Named.Util.getNames(getCipherFactories());

        List<Factory.Named<Cipher>> transportCiphers = Arrays.stream(ciphers)
                .filter(factoryNames::contains)
                .map(cipher -> Factory.Named.Util.get(getCipherFactories(), cipher))
                .collect(Collectors.toList());

        logger.info("Client-side cipher factories set to: {}", transportCiphers);

        setCipherFactories(transportCiphers);
    }

    public void setMacFactories(String input) {
        String[] macs = input.trim().split(",");
        List<String> factoryNames = Factory.Named.Util.getNames(getMACFactories());

        List<Factory.Named<MAC>> macCiphers = Arrays.stream(macs)
                .filter(factoryNames::contains)
                .map(mac -> Factory.Named.Util.get(getMACFactories(), mac))
                .collect(Collectors.toList());

        logger.info("Client-side MAC factories set to: {}", Factory.Named.Util.getNames(macCiphers));

        setMACFactories(macCiphers);
    }

    public void setKexFactories(String input) {
        String[] kexs = input.trim().split(",");
        List<String> factoryNames = Factory.Named.Util.getNames(getKeyExchangeFactories());

        List<Factory.Named<KeyExchange>> kexFactors = Arrays.stream(kexs)
                .filter(factoryNames::contains)
                .map(kex -> Factory.Named.Util.get(getKeyExchangeFactories(), kex))
                .collect(Collectors.toList());

        logger.info("Client-side MAC factories set to: {}", Factory.Named.Util.getNames(kexFactors));

        setKeyExchangeFactories(kexFactors);
    }
}

最后

因为需要SHA2 的加密,Jsch不支持,之前研究了下 Apache MINA SSHD,这个东西对于我这种只能用私钥访问的人来说太残忍了,密码访问倒是还行,也支持 sha2 ,有需要的小伙伴可以研究研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值