SFTP和秘钥连接传输

简述

SFTP可以为传输文件提供一种安全的网络的加密方法。sftp 与 ftp 有着几乎一样的语法和功能。SFTP 为 SSH的其中一部分,是一种传输档案至服务器的安全方式。其实在SSH软件包中,已经包含了一个叫作SFTP(Secure File Transfer Protocol)的安全文件信息传输子系统,SFTP本身没有单独的守护进程,它必须使用sshd守护进程(端口号默认是22)来完成相应的连接和答复操作,所以从某种意义上来说,SFTP并不像一个服务器程序,而更像是一个客户端程序。SFTP同样是使用加密传输认证信息和传输的数据,所以,使用SFTP是非常安全的。但是,由于这种传输方式使用了加密/解密技术,所以传输效率比普通的FTP要低得多,如果您对网络安全性要求更高时,可以使用SFTP代替FTP。linux开启了sshd就相当于开启了SFTP。

搭建步骤

描述:创建两个用户,一个为sftp管理员,剩余一个为指定目录的访问用户。sftp管理员对普通用户的sftp根目录下的内容具有读写权限,限制其他用户只能访问其自己的根目录且仅有读权限;相关的sftp用户不能登录到Linux系统中。

sftp的普通认证

1:确认openssh的版本:ssh -V(要求版本高于4.8p1,低于此版本需升级)
2:创建sftp管理组及用户组

[root@localhost ~]# groupadd sftpadmin

[root@localhost ~]# groupadd sftp

3:创建sftp管理用户及普通用户(-g 加入到sftp组 ,-s 禁止ssh登陆)

[root@localhost ~]# useradd -g sftp      -s /bin/false yeamin #/bin/false也可以替换为/sbin/nologin,目的是不允许该用户登录到系统中
[root@localhost ~]# useradd -g sftpadmin -s /bin/false admin
[root@localhost ~]# passwd yeamin
[root@localhost ~]# passwd admin

4:分别创建对应用户的sftp根目录并指定为其家目录

[root@localhost ~]# mkdir -pv /data/sftp/yeamin/share

mkdir: created directory `/data'

mkdir: created directory `/data/sftp'

mkdir: created directory `/data/sftp/yeamin'

mkdir: created directory `/data/sftp/yeamin/share'

注:多个用户可以写成 mkdir -pv /data/sftp/{A,B,C}/share
5:配置sshd_config文件

[root@localhost ~]# vim /etc/ssh/sshd_config
#Subsystem sftp /usr/libexec/openssh/sftp-server(找到如下这行,用#符号注释掉,大致在文件末尾处。)
Subsystem sftp internal-sftp     #这行指定使用sftp服务使用系统自带的internal-sftp
Match Group sftp     #这行用来匹配sftpusers组的用户,如果要匹配多个组,多个组之间用逗号分割; 
ChrootDirectory /data/sftp/%u        #用chroot将用户的根目录指定到%h,%h代表用户home目录,这样用户就只能在用户目录下活动。也可用%u,%u代表用户名。
ForceCommand internal-sftp    #指定sftp命令 
AllowTcpForwarding no
X11Forwarding no
Match user sftpadmin        #匹配用户了,多个用户名之间也是用逗号分割
ChrootDirectory /data/sftp
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no


6:- 设置Chroot目录的权限

[root@localhost ~]# chown root:sftp /data/sftp/yeamin        #修改普通用户的根目录属组

[root@localhost ~]# chmod 755 /data/sftp/yeamin               #修改普通用户的根目录权限

[root@localhost ~]# chown root:sftpadmin /data/sftp/        #修改管理员的根目录属组

[root@localhost ~]# chmod 755 /data/sftp/        #修改管理员根目录的权限

[root@localhost ~]# chown sftpadmin:sftp /data/sftp/yeamin/share/        #修改各普通用户下的share目录的属主为管理员,属组为普通用户组

[root@localhost ~]# chmod 750 /data/sftp/yeamin/share/        #各share目录管理员的权限为读写,sftp组仅有读权限,其他用户没有权限访问

7:关闭selinux

[root@localhost ~]# vim /etc/selinux/config 

SELINUX=permissive

[root@localhost ~]# setenforce 0

8:重启sshd服务:service sshd restart
9:验证sftp登录:sftp username@IP
10:设置记录sftp服务器的登录及操作日志

既然搭建了sftp服务器,那么通常还是需要知道哪些人在什么时候做了哪些操作,默认情况下sftp服务只会记录相关的登录信息,默认保存在/var/log/audit/audit.log日志下。但是默认的记录方式并不便于管理员获取指定的信息,因此我们可以对sftp日志进行另行的设置指定。

10.1:修改/etc/ssh/sshd_config

[root@localhost ~]# vim /etc/ssh/sshd_config 

Subsystem sftp internal-sftp -l VERBOSE -f AUTH,local5        #设置日志级别为VERBOSE,并指定日志记录的收集设施

Match Group sftp

ChrootDirectory /data/sftp/%u

ForceCommand internal-sftp -l VERBOSE        #设置日志级别为VERBOSE

AllowTcpForwarding no

X11Forwarding no

Match user sftpadmin

ChrootDirectory /data/sftp

ForceCommand internal-sftp -l VERBOSE        #设置日志级别为VERBOSE

AllowTcpForwarding no

X11Forwarding no

10.2:修改rsyslog.conf

[root@localhost ~]# vim /etc/rsyslog.conf

auth,authpriv.*,local5.* /var/log/sftp.log        #设置将相关的auth、authpriv及local5相关的日志信息记录到/var/log/sftp.log文件

11: 重启sshd和rsyslog

[root@localhost ~]# service sshd restart
[root@localhost ~]# service rsyslog restart

12:查看日志:cat /var/log/sftp.log
13:限制ssh连接的访问Ip

[root@localhost ~]# vim /etc/ssh/sshd_config 

# Authentication:
AllowUsers root@10.10.10.*        #限制root用户只能通过10.10.10.*网段登录访问
AllowUsers yeamin@10.10.10.*        #限制yeamin用户只能通过10.10.10.*网段登录访问

sftp的密钥认证

密钥登录无需用户设置密码,通过rsa密钥对加解密验证,在客户端和服务器端建立安全的连接,简单地说,public key放在服务器端,即下面配置的authorized_keys,private key放在客户端,客户端发起请求连接,服务器根据请求用户名识别对应客户端公钥,sshd服务产生一个随机数,用public key进行加密后,发回到客户端,客户端用private key解密得到该随机数,客户端将解密后的随机数发回服务器端,服务端进行匹配,匹配成功认证通过,允许登录。这种方式避免了密码暴力破解尝试的危险,当然密钥认证因为有加解密和随机数传输验证的过程,连接耗时自然比密码方式长些。
1:生成秘钥:ssh-keygen -t rsa(回车之后会让输入秘钥加密的账户,密码以及秘钥保存的地址等,也可以不输入)
2:将公钥传输到目标服务器(如果authorized_keys文件已存在,则追加到该文件的后面)

[root@localhost ~]# cp /root/.ssh/id_rsa.pub /home/charles/.ssh/authorized_keys     

3:设置秘钥相关的权限

chmod 600 /home/yeamin/.ssh/authorized_keys

chmod 700 /home/yeamin/.ssh

chown -R yeamin:sftp /home/yeamin/.ssh
ChrootDirectory设置的目录权限及其所有的上级文件夹权限,属主和属组必须是root; ChrootDirectory设置的目录权限及其所有的上级文件夹权限,只有属主能拥有写权限,也就是说权限最大设置只能是755。 4:为/etc/ssh/sshd_config添加配置
PermitRootLogin yes
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile	/root/.ssh/authorized_keys   #指出公钥的位置

注:如果你只允许用户使用密钥认证登录,可以设置/etc/ssh/sshd_config文PasswordAuthentication no
如果不改,密钥认证和密钥认证两种登录方式都允许。

5:测试密钥登录sftp

sftp -oIdentityFile=/root/.ssh/id_rsa username@IP

SSH/SSHD服务

  • 查看状态:
    systemctl status sshd.service
  • 启动服务:
    systemctl start sshd.service
  • 重启服务:
    systemctl restart sshd.service
  • 开机自启:
    systemctl enable sshd.service

java代码实现

  • pom.xml
<dependency>

      <groupId>com.jcraft</groupId>

      <artifactId>jsch</artifactId>

      <version>0.1.53</version>

</dependency>
  • 基于JSch包实现的SFTP服务器文件上传和下载功能代码如下:
public class SftpTool {
 
    public static final Logger logger = LoggerFactory.getLogger(SftpTool.class);
 
    private String host;
 
    private String username;
 
    private int port;
 
    public SftpTool(String host, String username, int port){
        this.host = host;
        this.username = username;
        this.port = port;
    }
 
    /**
     * 登陆sftp
     *
     * @param authTypeMode 认证方式
     * @return 登陆信息
     */
    public Map<String,Object> loginIn(AuthTypeMode authTypeMode){
        //解决JSch日志打印问题
        JSch.setLogger(new SettleJschLogPrint());
        try {
            ChannelSftp sftp = null;
            Session session = null;
 
            JSch jsch = new JSch();
            logger.info("获取SFTP服务器连接username:{},host:{},port:{}",username,host,port);
            session = jsch.getSession(username,host,port);
            logger.info("连接成功建立");
            if (AuthTypeEnum.RSA.getCode().equals(authTypeMode.getAuthType())){
                jsch.addIdentity(authTypeMode.getAuthValue(),"");
            }else {
                session.setPassword(authTypeMode.getAuthValue());
            }
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking","no");
            sshConfig.put("PreferredAuthentications","publickey,gssapi-with-mic,keyboard-interactive,password");
            session.setConfig(sshConfig);
            session.connect();
            logger.info("用户" + username + "成功登陆");
            Channel channel = session.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
 
            HashMap<String,Object> loginInfo = new HashMap<>();
            loginInfo.put("sftp",sftp);
            loginInfo.put("session",session);
            return loginInfo;
        } catch (JSchException e) {
            throw new RuntimeException("user login SFTP server occur exception:" + e);
        }
    }
 
    /**
     * 退出登陆
     *
     * @param sftp sftp对象
     * @param session session对象
     */
    public void loginOut(ChannelSftp sftp, Session session){
        try {
            if(null != sftp && sftp.isConnected()){
                sftp.disconnect();
            }
            if (null != session && session.isConnected()){
                session.disconnect();
            }
        } catch (Exception e) {
            logger.warn("用户退出SFTP服务器出现异常:" + e);
        }
    }
 
    /**
     * 下载文件
     *
     * @param downloadFilePath 要下载的文件所在绝对路径
     * @param downloadFileName 要下载的文件名(sftp服务器上的文件名)
     * @param saveFile 文件存放位置(文件所在绝对路径)
     * @param authTypeMode 用户认证方式
     */
    public void download(String downloadFilePath, String downloadFileName, File saveFile, AuthTypeMode authTypeMode) throws Exception{
        Assert.notNull(downloadFilePath,"download file absolute path is not null");
        Assert.notNull(downloadFileName,"download file is not null");
        Assert.notNull(saveFile,"save file location is not null");
        Assert.notNull(authTypeMode,"auth type way is not null");
 
        OutputStream outputStream = null;
        ChannelSftp sftp = null;
        Session session = null;
        try {
            Map<String,Object> loginInfo = loginIn(authTypeMode);
            sftp = (ChannelSftp) loginInfo.get("sftp");
            session = (Session) loginInfo.get("session");
 
            logger.info("待下载文件地址为:" + downloadFilePath + ",文件名为:" + downloadFileName + ",认证方式:" + authTypeMode.getAuthValue());
            sftp.cd(downloadFilePath);
            sftp.ls(downloadFileName);
            outputStream = new FileOutputStream(saveFile);
            sftp.get(downloadFileName,outputStream);
            logger.info("文件下载完成!");
 
        } finally {
            if (null != outputStream){
                outputStream.close();
            }
            loginOut(sftp,session);
        }
    }
 
    /**
     * 上传文件
     *
     * @param uploadPath 上传SFTP完整路径
     * @param uploadFile 上传文件(完整路径)
     * @param authTypeMode 认证方式
     */
    public void upload(String uploadPath, String uploadFile, AuthTypeMode authTypeMode) throws Exception{
        Assert.notNull(uploadPath,"upload path is not null");
        Assert.notNull(uploadFile,"upload file is not null");
        Assert.notNull(authTypeMode,"auth type way is not null");
 
        InputStream inputStream = null;
        ChannelSftp sftp = null;
        Session session = null;
        try {
            Map<String,Object> loginInfo = loginIn(authTypeMode);
            sftp = (ChannelSftp) loginInfo.get("sftp");
            session = (Session) loginInfo.get("session");
 
            logger.info("待上传文件为:" + uploadFile + ",上传SFTP服务器路径:" + uploadPath + ",认证方式:" + authTypeMode.getAuthValue());
            File file = new File(uploadFile);
            inputStream = new FileInputStream(file);
            try {
                sftp.cd(uploadPath);
            } catch (SftpException e) {
                logger.error("SFTP器服务存放文件路径不存在");
                throw new RuntimeException("upload path is not exist");
            }
            sftp.put(inputStream,file.getName());
            logger.info("上传文件成功!");
        } finally {
            if (null != inputStream){
                inputStream.close();
            }
            loginOut(sftp,session);
        }
 
    }
 
    /**
     * authType = PASSWORD,authValue = password
     * authType = rsa,authValue = rsa文件路径
     */
    class AuthTypeMode{
        private String authType;
 
        private String authValue;
 
        AuthTypeMode(String authType, String authValue){
            this.authType = authType;
            this.authValue = authValue;
        }
 
        String getAuthType(){
            return authType;
        }
 
        String getAuthValue(){
            return authValue;
        }
    }
 
    /**
     * 在slf4j日志框架里打印JSch日志
     */
    class SettleJschLogPrint implements com.jcraft.jsch.Logger{
 
        @Override
        public boolean isEnabled(int i) {
            return true;
        }
 
        @Override
        public void log(int i, String s) {
            logger.info(s);
        }
    }
 
    enum AuthTypeEnum {
 
        PASSWORD("PASSWORD","密码认证"),
 
        RSA("RSA","rsa密钥认证");
 
        private String code;
 
        private String desc;
 
        AuthTypeEnum(String code, String desc){
            this.code = code;
            this.desc = desc;
        }
 
        public String getCode(){
            return this.code;
        }
    }
 
    public static void main(String[] args) throws Exception{
        //密码认证方式
        SftpTool sftpTool = new SftpTool("192.168.79.151","albert",22);
        //下载文件
        File saveFile = new File("/data/sftp/verifyfile_01.txt");
        sftpTool.download("/verifyfile","verifyfile_01.csv",saveFile,sftpTool.new AuthTypeMode(AuthTypeEnum.PASSWORD.getCode(),"alan123456"));
        //上传文件
        sftpTool.upload("/verifyfile","/data/sftp/verifyfile_01.txt",sftpTool.new AuthTypeMode(AuthTypeEnum.PASSWORD.getCode(),"alan123456"));
 
        //密钥认证方式下载文件
        SftpTool sftpToolTwo = new SftpTool("192.168.79.151","qingniu",22);
        File saveFileTwo = new File("/data/sftp/verifyfile_01.txt");
        sftpToolTwo.download("/verifyfile","verifyfile_01.csv",saveFileTwo,sftpToolTwo.new AuthTypeMode(AuthTypeEnum.RSA.getCode(),"/data/rsa/id_rsa_qingniu"));
        //密钥认证方式上传文件( 上传会失败Permission denied,因为qingniu这个用户没有写的权限 )
        sftpToolTwo.upload("/verifyfile","/data/sftp/verifyfile_01.txt",sftpToolTwo.new AuthTypeMode(AuthTypeEnum.RSA.getCode(),"/data/rsa/id_rsa_qingniu"));
    }
}
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值