密码与验证码结合:如何通过 JSCH 和 SSHD 保护你的服务器

举报
bug菌 发表于 2024/09/30 11:07:46 2024/09/30
【摘要】 咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~🏆本文收录于「滚雪球学Java」专栏中,这个专栏专为有志于提升Java技能的你打造,覆盖Java编程的方方面面,助你从零基础到掌握Java开发的精髓。赶紧关注,收藏,学习吧!环境说明...

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~


🏆本文收录于「滚雪球学Java」专栏中,这个专栏专为有志于提升Java技能的你打造,覆盖Java编程的方方面面,助你从零基础到掌握Java开发的精髓。赶紧关注,收藏,学习吧!

环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

前言

在现代网络安全环境中,确保服务器的安全性变得尤为重要。传统的 SSH 密码登录机制已经不再足够安全,尤其是在面对暴力破解攻击和中间人攻击时。为了进一步提升服务器的安全性,开发者和运维人员通常会采取两步验证(Two-Factor Authentication, 2FA)的策略,将密码验证与动态验证码结合使用。这种方式大大提高了系统的防护能力。

本文将深入探讨如何通过 JSCH(Java Secure Channel)和 SSHD(SSH Daemon)保护服务器,结合密码与验证码的验证机制,实现更高的安全性。通过具体的代码示例和详细讲解,帮助读者理解如何在 Java 应用程序中集成这些安全技术。

SSH 安全性概述

SSH(Secure Shell)是一种用于在不安全网络上进行安全通信的协议,通常用于远程登录服务器。默认情况下,SSH 依赖密码或密钥进行验证,但随着攻击技术的进步,仅依靠单一的验证方式存在诸多安全隐患:

  • 暴力破解攻击:攻击者使用字典或猜测策略尝试破解密码。
  • 中间人攻击:攻击者可能伪装成目标服务器,窃取会话数据。
  • 凭证泄露:当密码或密钥泄露时,攻击者可以直接访问服务器。

为了应对这些安全威胁,密码验证的基础上增加动态验证码成为一种有效的解决方案。

为什么要结合密码与验证码

两步验证(2FA)将密码验证与动态验证码结合使用,显著增强了安全性。即使攻击者获取了用户的密码,他们仍然需要通过验证码验证才能登录,这大大增加了系统被攻破的难度。

验证码通常通过 TOTP(Time-based One-Time Password)生成,用户可以通过移动设备上的认证应用(如 Google Authenticator)生成基于时间的一次性密码。这种密码具有时效性,通常只能使用 30 秒,在过期后需要重新生成。

两步验证的优势:

  • 额外的安全层:即使密码泄露,攻击者无法访问用户的动态验证码。
  • 保护防护不受单一因素影响:单一密码或密钥不再是唯一的访问凭证,提升了安全性。
  • 时间敏感性:动态验证码有效时间短,避免了长时间暴露的风险。

JSCH 和 SSHD 简介

JSCH

JSCH 是一款 Java 实现的 SSH2 客户端库,允许 Java 程序通过 SSH 协议与服务器进行安全的通信。它广泛用于实现 SFTP 文件传输、远程命令执行等功能。

JSCH 的优点包括:

  • 轻量级:仅依赖于 Java 标准库,易于集成。
  • 功能全面:支持常见的 SSH 功能,如身份验证、端口转发、文件传输等。
  • 灵活扩展:可以与其他 Java 库结合,提供更复杂的功能。

SSHD

SSHD 是 SSH 协议的服务端实现,通常用于配置服务器的 SSH 服务。它允许服务器通过 SSH 与客户端进行安全的通信,并支持多种身份验证方式。

通过 SSHD,服务器可以:

  • 处理 SSH 连接请求。
  • 验证客户端的身份(如密码验证、公钥验证等)。
  • 启动 SSH 会话,执行命令或文件传输。

如何通过 JSCH 和 SSHD 实现密码与验证码验证

接下来,我们将通过一个实际案例演示如何通过 JSCH 和 SSHD 实现密码与验证码的结合验证。

步骤 1:服务器端配置(SSHD)

首先,我们需要在 SSHD 服务器上启用 TOTP(基于时间的一次性密码)来支持动态验证码。为了实现这一点,可以使用 libpam-google-authenticator 模块来生成和验证动态验证码。

安装 Google Authenticator PAM 模块

在 Linux 服务器上,首先安装 PAM 模块:

sudo apt-get install libpam-google-authenticator

然后,修改 SSHD 配置文件 /etc/ssh/sshd_config,确保以下设置:

ChallengeResponseAuthentication yes
UsePAM yes
AuthenticationMethods password,keyboard-interactive

这意味着服务器将同时要求密码和动态验证码来进行身份验证。

接着,编辑 PAM 配置文件 /etc/pam.d/sshd,在文件末尾添加以下内容:

auth required pam_google_authenticator.so

最后,重启 SSHD 服务:

sudo systemctl restart sshd

为用户启用两步验证

在 SSHD 服务器上,为需要启用两步验证的用户生成动态验证码:

google-authenticator

根据提示设置相关选项,并将生成的二维码添加到 Google Authenticator 等支持 TOTP 的应用中。此时,该用户需要在登录时输入密码和动态验证码。

步骤 2:客户端配置(JSCH)

在客户端,我们使用 JSCH 连接 SSH 服务器,并通过扩展 JSCH 的身份验证机制,向服务器提供密码和动态验证码。

首先,添加 JSCH 依赖到 Maven 项目中:

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

接着,编写代码示例,展示如何通过 JSCH 进行 SSH 登录:

import com.jcraft.jsch.*;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class SSHClientWithOTP {
    public static void main(String[] args) {
        String host = "your-server-ip";
        String user = "your-username";
        String password = "your-password";  // 密码
        String otp = "your-otp-code";  // 动态验证码

        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(user, host, 22);
            session.setPassword(password);

            // 添加主机密钥验证策略
            session.setConfig("StrictHostKeyChecking", "no");

            // 登录之前输入动态验证码
            session.setUserInfo(new UserInfo() {
                @Override
                public String getPassword() {
                    return password;
                }

                @Override
                public boolean promptYesNo(String str) {
                    return true;
                }

                @Override
                public String getPassphrase() {
                    return null;
                }

                @Override
                public boolean promptPassphrase(String str) {
                    return false;
                }

                @Override
                public boolean promptPassword(String str) {
                    System.out.println("Enter OTP:");
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                        otp = reader.readLine();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return true;
                }

                @Override
                public void showMessage(String str) {
                    System.out.println(str);
                }
            });

            // 建立连接
            session.connect();

            // 执行命令
            ChannelExec channelExec = (ChannelExec) session.openChannel("exec");
            channelExec.setCommand("ls");
            channelExec.connect();

            BufferedReader in = new BufferedReader(new InputStreamReader(channelExec.getInputStream()));
            String msg;
            while ((msg = in.readLine()) != null) {
                System.out.println(msg);
            }

            // 关闭连接
            channelExec.disconnect();
            session.disconnect();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们通过 JSCH 提供了两种身份验证:密码和动态验证码。用户在输入密码后,程序会提示用户输入从 Google Authenticator 应用生成的验证码。

代码解析:

  在本次的代码演示中,我将会深入剖析每句代码,详细阐述其背后的设计思想和实现逻辑。通过这样的讲解方式,我希望能够引导同学们逐步构建起对代码的深刻理解。我会先从代码的结构开始,逐步拆解每个模块的功能和作用,并指出关键的代码段,并解释它们是如何协同运行的。通过这样的讲解和实践相结合的方式,我相信每位同学都能够对代码有更深入的理解,并能够早日将其掌握,应用到自己的学习和工作中。

如上段Java代码演示了如何使用JSch库来建立一个SSH连接,并在登录过程中输入动态验证码(OTP)。下面是代码的逐行解读:

  1. import 语句导入了JSch库和其他必要的类。

  2. public class SSHClientWithOTP 定义了一个名为SSHClientWithOTP的公共类。

  3. public static void main(String[] args) 是程序的入口点。

  4. 定义了连接SSH服务器所需的变量:host(服务器IP地址)、user(用户名)、password(密码)和otp(动态验证码)。

  5. try 块开始,用于捕获和处理可能发生的异常。

  6. JSch jsch = new JSch(); 创建了一个JSch对象。

  7. Session session = jsch.getSession(user, host, 22); 创建了一个SSH会话对象。

  8. session.setPassword(password); 为SSH会话设置了密码。

  9. session.setConfig("StrictHostKeyChecking", "no"); 设置了会话配置,禁用了严格的主机密钥检查。

  10. session.setUserInfo(new UserInfo() {...}); 设置了一个UserInfo对象,用于处理登录过程中的用户交互。

  11. @Override 注解用于覆盖UserInfo接口的方法。

  12. public String getPassword() 方法返回密码。

  13. public boolean promptYesNo(String str) 方法用于提示用户是否继续,这里总是返回true

  14. public String getPassphrase()public boolean promptPassphrase(String str) 方法用于处理密钥短语,这里不使用,所以返回nullfalse

  15. public boolean promptPassword(String str) 方法在需要输入动态验证码时被调用。它提示用户输入OTP,并从标准输入读取。

  16. public void showMessage(String str) 方法用于显示消息。

  17. session.connect(); 尝试建立SSH连接。

  18. ChannelExec channelExec = (ChannelExec) session.openChannel("exec"); 创建了一个执行命令的通道。

  19. channelExec.setCommand("ls"); 设置了要执行的命令,这里是列出当前目录下的文件。

  20. channelExec.connect(); 建立命令执行通道的连接。

  21. 使用BufferedReader读取命令执行的输出。

  22. while 循环读取并打印命令的输出。

  23. channelExec.disconnect();session.disconnect(); 分别关闭命令执行通道和SSH会话。

  24. catch 块捕获并打印异常信息。

总而言之,这段代码展示了如何在SSH登录过程中处理动态验证码。在实际应用中,应该使用更安全的认证方式,如基于密钥的认证,并且应该验证服务器的公钥以确保连接的安全性。此外,密码和OTP不应该硬编码在代码中,而应该通过安全的方式获取。

步骤 3:测试与验证

通过上述代码和配置,启动 Java 客户端程序并连接到 SSHD 服务器。在连接时,用户首先输入密码,随后系统会要求用户输入动态验证码。验证通过后,服务器允许用户进行进一步操作。

生产环境中的最佳实践

在生产环境中,采用密码和动态验证码结合的验证方式能够大大提高系统的安全性。为了确保系统运行的稳定性和安全性,建议遵循以下几项最佳实践:

  1. 定期更换密码与密钥:即使使用了两步验证,也应定期更换 SSH 密码和密钥,防止过期凭据被滥用。
  2. 启用 Fail2Ban:配置 Fail2Ban 等工具,限制登录失败次数,防止暴力破解攻击。
  3. 使用强密码策略:确保使用复杂的密码,避免简单或常见密码被暴力破解。
  4. 启用 SSH 日志监控:定期检查 SSH 登录日志,及时发现异常登录行为。

拓展内容:SSH 的其他安全增强措施

除了两步验证之外,还有多种手段可以进一步增强 SSH 的安全性:

  • 基于公钥的认证:相比于密码,基于公钥的认证更为安全。使用 RSA 或 ECDSA 密钥对进行身份验证,可以避免密码

泄露带来的风险。

  • 端口转发与跳板机:通过 SSH 实现端口转发或使用跳板机,能够隐藏目标服务器的真实 IP 地址,增加攻击者的难度。
  • 限制 SSH 访问权限:通过配置防火墙和 hosts.deny/hosts.allow 文件,限制 SSH 的访问来源,避免外部网络对 SSH 端口进行扫描和攻击。

结论

通过结合密码和动态验证码(TOTP),我们可以大幅提升 SSH 登录的安全性。在本文中,我们展示了如何使用 JSCH 和 SSHD 实现两步验证,从而有效地保护服务器免受暴力破解和凭据泄露的威胁。结合实际案例,我们可以看到两步验证的易用性和强大保护能力。此外,通过进一步增强 SSH 的安全配置,您可以确保服务器在互联网环境中更加安全可靠。


这篇文章展示了如何通过密码和动态验证码结合的方式加强 SSH 安全性,并结合了具体的 JSCH 和 SSHD 配置示例,帮助读者更好地理解和应用这一技术。同时,本文还提供了额外的安全增强措施,以确保在生产环境中的服务器安全。

☀️建议/推荐你

无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

码字不易,如果这篇文章对你有所帮助,帮忙给bug菌来个一键三连(关注、点赞、收藏) ,您的支持就是我坚持写作分享知识点传播技术的最大动力。
  同时也推荐大家关注我的硬核公众号:「猿圈奇妙屋」 ;以第一手学习bug菌的首发干货,不仅能学习更多技术硬货,还可白嫖最新BAT大厂面试真题、4000G Pdf技术书籍、万份简历/PPT模板、技术文章Markdown文档等海量资料,你想要的我都有!

📣关于我

我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,掘金等平台签约作者,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计30w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。


–End

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。