1、SFTP信用公钥配置
1.1 客户端生成密钥对
以DSA举例:
ssh-keygen –t dsa
执行该命令后,在home/用户名/.ssh目录下,会生成id_dsa和id_dsa.pub两个文件
1.2 将id_dsa.pub公钥文件上传至服务端的home/用户名/.ssh目录下
scp id_dsa.pub 用户名@服务端IP:/home/用户名/.ssh
此时还需要输入密码
1.3服务端添加信任公钥
登录服务端,进入到/home/用户名/.ssh目录,将刚刚拷贝的id_dsa.pub文件的内容加入到authorized_keys文件中
cat id_dsa.pub >> authorized_keys
1.4 服务端分别修改authorized_key文件和.ssh的权限为600和700
chmod 600 authorized_keys
chmod 700 .ssh
1.5 测试
在客户端执行:
sftp –oPort=端口 用户名@服务端IP
如果不需要输入密码就可以连上,则说明配置成功
2、基于JSCH库的sftp操作
public class SftpUtil {
private final static Logger log = LoggerFactory.getLogger(SftpUtil.class);
/** SFTP */
public static final String SFTP = "sftp";
/** 通道 */
private ChannelSftp channel;
/** session */
private Session session;
/** 规避多线程并发 */
private static ThreadLocal<SftpUtil> sftpLocal = new ThreadLocal<SftpUtil>();
/**
* 获取sftpchannel
*
* @param connectConfig 连接配置
* @return
* @throws Exception
* @throws JSchException
*/
private void init(ConnectConfig connectConfig) throws Exception {
String host = connectConfig.getHost();
int port = connectConfig.getPort();
String userName = connectConfig.getUserName();
//创建JSch对象
JSch jsch = new JSch();
//添加私钥(信任登录方式)
if (StringUtils.isNotBlank(connectConfig.getPrivateKey())) {
jsch.addIdentity(connectConfig.getPrivateKey());
}
session = jsch.getSession(userName, host, port);
if (log.isInfoEnabled()) {
log.info(" JSCH Session created,sftpHost = {}, sftpUserName={}", host, userName);
}
//设置密码
if (StringUtils.isNotBlank(connectConfig.getPassWord())) {
session.setPassword(connectConfig.getPassWord());
}
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
//设置超时
session.setTimeout(connectConfig.getTimeout());
//建立连接
session.connect();
if (log.isInfoEnabled()) {
log.info("JSCH Session connected.sftpHost = {}, sftpUserName={}", host, userName);
}
//打开SFTP通道
channel = (ChannelSftp) session.openChannel(SFTP);
//建立SFTP通道的连接
channel.connect();
if (log.isInfoEnabled()) {
log.info("Connected successfully to sftpHost = {}, sftpUserName={}", host, userName);
}
}
/**
* 是否已连接
*
* @return
*/
private boolean isConnected() {
return null != channel && channel.isConnected();
}
/**
* 获取本地线程存储的sftp客户端
*
* @return
* @throws Exception
*/
public static SftpUtil getSftpUtil(ConnectConfig connectConfig) throws Exception {
SftpUtil sftpUtil = sftpLocal.get();
if (null == sftpUtil || !sftpUtil.isConnected()) {
sftpLocal.set(new SftpUtil(connectConfig));
}
return sftpLocal.get();
}
/**
* 释放本地线程存储的sftp客户端
*/
public static void release() {
if (null != sftpLocal.get()) {
sftpLocal.get().closeChannel();
sftpLocal.set(null);
}
}
/**
* 构造函数
* <p>
* 非线程安全,故权限为私有
* </p>
*
* @throws Exception
*/
private SftpUtil(ConnectConfig connectConfig) throws Exception {
super();
init(connectConfig);
}
/**
* 关闭通道
*
* @throws Exception
*/
public void closeChannel() {
if (null != channel) {
try {
channel.disconnect();
} catch (Exception e) {
log.error("关闭SFTP通道发生异常:", e);
}
}
if (null != session) {
try {
session.disconnect();
} catch (Exception e) {
log.error("SFTP关闭 session异常:", e);
}
}
}
/**
* 下载文件
*
* @param downDir 下载目录
* @param src 源文件
* @param dst 保存后的文件名称或目录
* @throws Exception
*/
public void downFile(String downDir, String src, String dst) throws Exception {
channel.cd(downDir);
channel.get(src, dst);
}
/**
* 删除文件
*
* @param filePath 文件全路径
* @throws SftpException
*/
public void deleteFile(String filePath) throws SftpException {
channel.rm(filePath);
}
@SuppressWarnings("unchecked")
public List<String> listFiles(String dir) throws SftpException {
Vector<LsEntry> files = channel.ls(dir);
if (null != files) {
List<String> fileNames = new ArrayList<String>();
Iterator<LsEntry> iter = files.iterator();
while (iter.hasNext()) {
String fileName = iter.next().getFilename();
if (StringUtils.equals(".", fileName) || StringUtils.equals("..", fileName)) {
continue;
}
fileNames.add(fileName);
}
return fileNames;
}
return null;
}
}
说明:
2.1 ConnectConfig包含了建立sftp连接所需要的全部参数信息
2.2 如果按照第一步进行了sftp的信任公钥配置,则需要通过调用jsch的addIdentity方法将密钥对中的私钥id_dsa设置进去
//添加私钥(信任登录方式)
if (StringUtils.isNotBlank(connectConfig.getPrivateKey())) {
jsch.addIdentity(connectConfig.getPrivateKey());
}
2.3 为了避免频繁的进行连接建立和连接释放操作,一般会定义为单例模式,但存在某些业务场景,需要在同一个线程执行完连续几次完整的业务操作后,将连接释放掉。如果采用单例,那么多线程并发的场景下会出现共享资源竞争导致的并发问题,譬如在 B 线程执行业务的过程中, A 线程将连接释放。因此,可以借助 ThreadLocal 来避免该问题。
/**
* 获取本地线程存储的sftp客户端
*
* @return
* @throws Exception
*/
public static SftpUtil getSftpUtil(ConnectConfig connectConfig) throws Exception {
SftpUtil sftpUtil = sftpLocal.get();
if (null == sftpUtil || !sftpUtil.isConnected()) {
sftpLocal.set(new SftpUtil(connectConfig));
}
return sftpLocal.get();
}
/**
* 释放本地线程存储的sftp客户端
*/
public static void release() {
if (null != sftpLocal.get()) {
sftpLocal.get().closeChannel();
sftpLocal.set(null);
}
}