nginx正向代理SFTP整体配置方案

一、概述
目前由于行内网络规划以及安全的原因,不能直接从应用区域直接访问第三方SFTP文件服务器,只能允许代理服务器网络区域出去,也就是SFTP正向代理转发到第三方的SFTP文件服务器,而我们使用的代理应用软件则是开源的nginx。以前搞过正向代理HTTP,而没有尝试过正向代理SFTP,其实也就是TCP协议。为了满足应用需求,我们需要搭建nginx正向代理SFTP服务应用。

nginx从1.9.0开始,新增加了一个stream模块,用来实现四层协议的转发、代理或者负载均衡等。这完全就是抢HAproxy份额的节奏,鉴于nginx在7层负载均衡和web service上的成功,和nginx良好的框架,stream模块前景一片光明。

ngx_stream_core_module模块
是模拟反代基于tcp或udp的服务连接,即工作于传输层的反代或调度器

二、Nginx编译安装步骤

卸载

linux有一系列的软件管理器,比如常见的linux下的yum、Ubuntu下的apt-get等等。通过这些软件管理器可以很快的卸载软件,并且不会有文件及配置残留。这里我使用的是yum,命令如下

yum remove nginx

1、首先,要准备的是软件,可以在网上下载,http://nginx.org/en/download.html,我安装的是

nginx-1.16.0  pgp

 

2.解压并切换到安装目录

tar -zxvf nginx-1.16.0.tar.gz

cd nginx-1.16.0

3.编译安装

./configure --prefix=/xjld/app/nginx --sbin-path=/xjld/app/nginx/sbin/nginx --conf-path=/xjld/app/nginx/conf/nginx.conf --with-http_stub_status_module --with-http_gzip_static_module --with-stream


make && make install
检测是否安装完成:出现如下,则说明安装成功

Nginx启动和端口查看

4.修改配置文件

vim /xjld/app/nginx/conf/nginx.conf(在配置文件最后行添加如下)
PS:这个模块一定要放在http外面

s

stream {
upstream dnc_sftp {
hash $remote_addr consistent;
server 172.18.58.41:22 max_fails=3 fail_timeout=30s;
}

server{
listen 8080;
proxy_connect_timeout 5s;
proxy_timeout 5s;
proxy_pass dnc_sftp;
}

}

解析:

如上配置文件的含义为
将端口8080反向代理NAME1组的serverIP:PORT,最大失败次数为3,超时时间为30秒;
将端口60000反向代理NAME2组的serverIP:PORT,最大失败次数为3,超时时间为30秒。

5.检测语法

/opt/nginx/sbin/nginx -t

6.开启NGINX

/opt/nginx/sbin/nginx

7.重启NGINX

/opt/nginx/sbin/nginx -s reload

这里推荐使用reload而不是restart。


stream core 一些变量
(注意:变量支持是从 nginx 1.11.2版本开始的)
$binary_remote_addr
二进制格式的客户端地址
$bytes_received
从客户端接收到的字节数
$bytes_sent
发往客户端的字节数
$hostname
连接域名
$msec
毫秒精度的当前时间
$nginx_version
nginx 版本
$pid
worker进程号
$protocol
通信协议(UDP or TCP)
$remote_addr
客户端ip
$remote_port
客户端端口
$server_addr
接受连接的服务器ip,计算此变量需要一次系统调用。所以避免系统调用,在listen指令里必须指定具体的服务器地址并且使用参数bind。
$server_port
接受连接的服务器端口
$session_time
毫秒精度的会话时间(版本1.11.4开始)
$status
会话状态(版本1.11.4开始), 可以是一下几个值:
200
成功
400
不能正常解析客户端数据
403
禁止访问
500
服务器内部错误
502
网关错误,比如上游服务器无法连接
503
服务不可用,比如由于限制连接等措施导致
$time_iso8601
ISO 8601时间格式
$time_local
普通日志格式的时间戳
 

五.eclipse导入maven项目,修改配置文件,maven版本(apache-maven-3.3.3.rar)

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


六.测试

package dnc_util;

import com.ctid.util.file.SFtpUtil;

public class TestSftp {

public static void main(String[] args) throws Exception {
String host = "172.18.58.XX";
String username = "appmanagXX";
String password = "GBhnjm5XX";
int port = 22;
int timeOut = 10*1000;

String localFile = "D:/sftptest/New-net.log";
String destPath ="/xjld/test/te";

SFtpUtil st =new SFtpUtil();
st.connect(host, username, password, port, timeOut);
st.upload(localFile, destPath, "old.log", "New.log");

st.disconnect();

}

}

sftp工具类

package com.ctid.util.file;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ctid.core.exception.ServiceException;
import com.ctid.core.util.StringUtil;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
/**
* 用于SFTP文件上传下载
* @date 2019-06-14
* @author 王伟
*/
public class SftpUtil implements AutoCloseable{


private final Log LOGGER = LogFactory.getLog(SftpUtil.class);

//创建sftp通信通道
private ChannelSftp sftp = null;
private Session session = null;
//文件分割符,默认为linux系统
private static final String SEPARATOR = "/";


//SFTP协议
private final String SFTP_PROTOCAL = "sftp";
//默认登陆超时时间15s
private final int LOGON_TIME_OUT = 15*1000;
// //默认session中socket超时时间30分钟
private final int SOCKET_TIME_OUT = 30*60*60*1000;

/**
* 根据输入参数获取SFTP链接
* @param host 主机IP
* @param username 主机登陆用户名
* @param password 主机登陆密码
* @param port 主机ssh登陆端口,如果port <= 0取默认值(22)
* @param timeOut 登录超时,通道连接超时时间
* @throws Exception
* @see http://www.jcraft.com/jsch/
*/
public boolean connect(String host, String username, String password, int port,int timeOut) throws Exception {
boolean result = false;
Channel channel = null;
JSch jsch = new JSch();
if (timeOut < 0) {
timeOut = LOGON_TIME_OUT;
}
session = createSession(jsch, host, username, port);
// 设置登陆主机的密码
session.setPassword(password);
session.setTimeout(SOCKET_TIME_OUT);
// 设置登陆超时时间
session.connect(timeOut);
try {
// 创建sftp通信通道
channel = (Channel) session.openChannel(SFTP_PROTOCAL);
channel.connect(timeOut);
sftp = (ChannelSftp) channel;
//设置根路径,远程以及本地,对于window系统不适用,window需要写绝对路径到盘符
sftp.cd(SEPARATOR);
sftp.lcd(SEPARATOR);
result = true;
} catch (JSchException e) {
LOGGER.error("exception when channel create. "+e.getMessage(), e);
throw new ServiceException("exception when channel create. "+e.getMessage(), e);
}
return result;
}

/**
* Private/public key authorization (加密秘钥方式登陆)
* @param username 主机登陆用户名(user account)
* @param host 主机IP(server host)
* @param port 主机ssh登陆端口(ssh port), 如果port<=0, 取默认值22
* @param privateKey 秘钥文件路径(the path of key file.)
* @param passphrase 密钥的密码(the password of key file.)
* @param timeOut 登录超时,通道连接超时时间
* @throws Exception
* @see http://www.jcraft.com/jsch/
*/
public boolean connect(String username, String host, int port, String privateKey, String passphrase,int timeOut)
throws Exception {
boolean result = false;
Channel channel = null;
JSch jsch = new JSch();
if (timeOut < 0) {
timeOut = LOGON_TIME_OUT;
}
// 设置密钥和密码 ,支持密钥的方式登陆
if (!StringUtil.isEmpty(privateKey)) {
if (!StringUtil.isEmpty(passphrase)) {
// 设置带口令的密钥
jsch.addIdentity(privateKey, passphrase);
} else {
// 设置不带口令的密钥
jsch.addIdentity(privateKey);
}
}
Session session = createSession(jsch, host, username, port);
// 设置登陆超时时间
session.connect(timeOut);
try {
// 创建sftp通信通道
channel = (Channel) session.openChannel(SFTP_PROTOCAL);
channel.connect(timeOut);
sftp = (ChannelSftp) channel;
//设置根路径,远程以及本地,对于window系统不适用,window需要写绝对路径到盘符
sftp.cd(SEPARATOR);
sftp.lcd(SEPARATOR);
result = true;
} catch (JSchException e) {
LOGGER.error("exception when channel create."+e.getMessage(), e);
throw new ServiceException("exception when channel create."+e.getMessage(), e);
}
return result;
}

/**
* upload the file to the server<br/>
* 将本地文件名为 localFile 的文件上传到目标服务器, 目标文件名为 destFile,<br/>
* 采用默认的传输模式: OVERWRITE 覆盖式推送
* @param sftp
* @param localFile 本地文件的绝对路径
* @param destFile 目标文件的绝对路径
* @throws ServiceException
*/
public boolean upload(String localFile, String destFile) throws ServiceException {
boolean result = false;
try {
File file = new File(localFile);
if (file.isDirectory()) {
for (String fileName : file.list()) {
sftp.put(FileUtil.setDirectoryFile(localFile, fileName), destFile,ChannelSftp.OVERWRITE);
}
} else {
sftp.put(localFile, destFile, ChannelSftp.OVERWRITE);
}
result = true;
} catch (Exception e) {
LOGGER.error(localFile+" upload to "+destFile+" exception "+e.getMessage(), e);
throw new ServiceException(localFile+" upload to "+destFile+" exception "+e.getMessage(), e);
}
return result;
}

/**
* upload the file to the server<br/>
* 将本地文件名为 localFile 的文件上传到目标服务器, 目标路径为destPath,<br/>
* 目标文件名称为destFileName
* 采用默认的传输模式: OVERWRITE 覆盖式推送
* @param sftp
* @param localFile 本地文件的绝对路径
* @param destPath 目标文件的绝对目录路径
* @param destFileName 目标文件名称
* @throws ServiceException
*/
public boolean upload(String localFile, String destPath,String destFileName) throws ServiceException {
boolean result = false;
String destFile = "";
try {
//创建远程路径
createRemotedir(destPath);
//拼接远程路径+文件名
destFile = FileUtil.setDirectoryFile(destPath, destFileName);
sftp.put(localFile,destFile,ChannelSftp.OVERWRITE);
result = true;
} catch (Exception e) {
LOGGER.error(localFile+" upload to "+destFile+" exception "+e.getMessage(), e);
throw new ServiceException(localFile+" upload to "+destFile+" exception "+e.getMessage(), e);
}
return result;
}


/**
* upload the file to the server and rename file<br/>
* 将本地文件名为 localFile 的文件上传到目标服务器, 目标路径为destPath,<br/>
* 目标文件名称为destFileName,重命名后的文件名称为newFileName
* 采用默认的传输模式: OVERWRITE 覆盖式推送
* @param sftp
* @param localFile 本地文件的绝对路径
* @param destPath 目标文件的绝对目录路径
* @param destFileName 目标文件名称
* @param newFileName 重命名后的名称
* @throws ServiceException
*/
public boolean upload(String localFile, String destPath,String destFileName,String newFileName) throws ServiceException {
boolean result = false;
String destFile = destPath;
String newFile = "";
try {
//创建远程路径
createRemotedir(destPath);
//拼接远程路径+文件名
destFile = FileUtil.setDirectoryFile(destPath, destFileName);
sftp.put(localFile,destFile,ChannelSftp.OVERWRITE);
//新文件名不为空,拼接重命名后的新文件,并重新命名
if(!StringUtil.isEmpty(newFileName)){
newFile = FileUtil.setDirectoryFile(destPath, newFileName);
sftp.rename(destFile, newFile);
}
result =true;
} catch (Exception e) {
LOGGER.error(localFile+" upload to "+destFile+" exception "+e.getMessage(), e);
throw new ServiceException(localFile+" upload to "+destFile+" exception "+e.getMessage(), e);
}
return result;
}

/**
* 使用sftp下载文件,若本地存储路径下存在与下载重名的文件,忽略这个文件
* @param remotePath 服务器上源文件的路径, 必须是目录
* @param savePath 下载后文件的存储路径, 必须是目录
* @param remoteFileName 服务器上的文件名称
* @throws ServiceException
*/
public boolean download(String remotePath, String savePath,String remoteFileName) throws ServiceException {
boolean result = false;
try {
//切换到远程对应remotePath目录下
sftp.cd(remotePath);
//创建本地目录savePath
createLocalDir(savePath);
File localFile = new File(FileUtil.setDirectoryFile(savePath, remoteFileName));
// savePath路径下已有文件与下载文件重名, 忽略这个文件
if (localFile.exists() && localFile.isFile()) {
return true;
}
//下载远程文件到savePath,文件名称为remoteFile
sftp.get(remoteFileName, localFile+"");
result = true;
} catch (Exception e) {
LOGGER.error(remotePath+SEPARATOR+remoteFileName+" download to "+savePath+" exception "+e.getMessage(), e);
throw new ServiceException(remotePath+SEPARATOR+remoteFileName+" download to "+savePath+" exception " +e.getMessage(), e);
}
return result;
}

/**
* sftp下载目标服务器上remotePath目录下所有指定的文件.<br/>
* 若本地存储路径下存在与下载重名的文件,忽略这个文件.<br/>
* @param remotePath 服务器上源文件的路径, 必须是目录
* @param savePath 文件下载到本地存储的路径,必须是目录
* @param fileList 指定的要下载的文件名列表
* @throws ServiceException
*/
public boolean downloadFileList(String remotePath, String savePath,List<String> fileList) throws ServiceException {
boolean result = false;
try {
sftp.cd(remotePath);
String localFile = "";
for (String srcFile : fileList) {
try {
localFile = FileUtil.setDirectoryFile(savePath, savePath);
File file = new File(localFile);
// savePath路径下已有文件与下载文件重名, 忽略这个文件
if (file.exists() && file.isFile()) {
continue;
}
sftp.get(srcFile, localFile);
} catch (Exception e) {
LOGGER.error(remotePath + SEPARATOR + srcFile+ " download to " + localFile + " exception "+e.getMessage(), e);
}
}
result = true;
} catch (Exception e) {
LOGGER.error(remotePath + " download to " + savePath + " exception "+e.getMessage(), e);
throw new ServiceException(remotePath + " download to " + savePath + " exception "+e.getMessage(), e);
}
return result;
}

/**
* 删除文件
* @param dirPath 要删除文件所在目录
* @param file 要删除的文件
* @param sftp
* @throws SftpException
*/
public boolean delete(String dirPath, String file) throws SftpException {
sftp.cd(SEPARATOR);
String now = sftp.pwd();
sftp.cd(dirPath);
sftp.rm(file);
sftp.cd(now);
return true;
}

/**
* 删除文件
* @param filePath 要删除文件的路径
* @param sftp
* @throws SftpException
*/
public boolean delete(String filePath) throws SftpException {
sftp.cd(SEPARATOR);
String now = sftp.pwd();
sftp.cd(getDirectory(filePath));
sftp.rm(getFileName(filePath));
sftp.cd(now);
return true;
}


/**
* @功能描述 从路径中抽取文件名
* @param path 路径
* @return 返回文件名
*/
private String getFileName(String path) {
try {
if (path.contains("\")) {
return path.substring(path.lastIndexOf("\") + 1);
}else if(path.contains("/")){
return path.substring(path.lastIndexOf("/") + 1);
}else{
return path;
}
} catch (Exception e) {
return null;
}
}
/**
* @功能描述 从路径中抽取目录
* @param path 路径
* @return 返回目录
*/
private String getDirectory(String filePath) {
try {
if (filePath.contains("\")) {
return filePath.substring(0, filePath.lastIndexOf("\") + 1);
}
return new File(filePath).getPath().substring(0, filePath.lastIndexOf("/") + 1);
} catch (Exception e) {
return null;
}
}
/**
* 获取remotePath路径下以regex格式指定的文件列表,传""或者null默认获取所有文件
* @param remotePath sftp服务器上的目录
* @param regex 需要匹配的文件名,
* @return 获取的文件列表
* @throws SftpException
*/
@SuppressWarnings("unchecked")
public List<String> listFiles(String remotePath, String regex) throws SftpException {
List<String> fileList = new ArrayList<String>();
try{
// 如果remotePath不是目录则会抛出异常
sftp.cd(remotePath);
if ("".equals(regex) || regex == null) {
regex = "*";
}
Vector<LsEntry> sftpFiles = sftp.ls(regex);
String fileName = null;
for (LsEntry lsEntry : sftpFiles) {
fileName = lsEntry.getFilename();
fileList.add(fileName);
}
}catch (Exception e) {
LOGGER.error("get file list from path :"+remotePath + " exception "+e.getMessage(), e);
fileList = null;
}
return fileList;
}

/**
* 根据用户名,主机ip,端口获取一个Session对象
* @param jsch jsch对象
* @param host 主机ip
* @param username 用户名
* @param port 端口
* @return
* @throws Exception
*/
private Session createSession(JSch jsch, String host, String username,int port) throws Exception {
Session session = null;
if (port <= 0) {
// 连接服务器,采用默认端口
session = jsch.getSession(username, host);
} else {
// 采用指定的端口连接服务器
session = jsch.getSession(username, host, port);
}
// 如果服务器连接不上,则抛出异常
if (session == null) {
throw new Exception(host + "session is null");
}
// 设置第一次登陆的时候提示,可选值:(ask | yes | no)
session.setConfig("StrictHostKeyChecking", "no");
return session;
}

/**
* 循环创建远程路径
* @param destDirPath
* @throws SftpException
*/
private void createRemotedir(String destDirPath) throws SftpException {
//设置根路径
sftp.cd(SEPARATOR);
String[] folders = destDirPath.split(SEPARATOR);
for (String folder : folders) {
if (folder.length() > 0) {
try {
// 如果folder不存在,则会报错,此时捕获异常并创建folder路径
sftp.cd(folder);
} catch (SftpException e) {
sftp.mkdir(folder);
sftp.cd(folder);
}
}
}
}
/**
* 创建本地路径
* @param savePath 本地文件路径
* @return
* @throws Exception
*/
private File createLocalDir(String savePath) throws Exception {
File localPath = new File(savePath);
if (!localPath.exists() && !localPath.isFile()) {
if (!localPath.mkdirs()) {
throw new Exception(localPath + " directory can not create. ");
}
}
return localPath;
}




/**
* Disconnect with server
* 断开sftp连接
*/
public void disconnect() {
try {
if (sftp != null) {
sftp.quit();
}
if (sftp != null) {
sftp.disconnect();
}
if (session != null) {
session.disconnect();
}
} catch (Exception e) {
sftp = null;
session = null;
}
}

@Override
public void close() throws Exception {
sftp.disconnect();
}

}

原文地址:https://www.cnblogs.com/pinghengxing/p/11043674.html