密码与验证码结合:如何通过 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) 方法用于处理密钥短语,这里不使用,所以返回null和false。

  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个月内不可修改。