关于 Java 中使用 smtp 进行邮箱认证的 bug 疑惑

23次阅读

共计 1888 个字符,预计需要花费 5 分钟才能阅读完成。

  1. 代码片段
    public boolean authBySMTP(String name, String password) throws IOException {String encodedName = BaseEncoding.base64().encode(name.getBytes());
        String encodedPassword = BaseEncoding.base64().encode(password.getBytes());
        boolean authed = false;

        Socket socket = new Socket("smtp.exmail.qq.com", 25);
        socket.setSoTimeout(10000);

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));) {writer.write("helo" + "smtp.exmail.qq.com" + "rn");
            writer.write("auth login" + "rn");
            writer.write(encodedName + "rn");
            writer.write(encodedPassword + "rn");
            writer.write("quit" + "rn");
            writer.flush();

            String response = null;
            String code = null;
            while ((response = reader.readLine()) != null) {code = response.substring(0, 3);
                if ("235".equals(code)){ // 返回码 235 表示认证成功
                    authed = true;
                }
                if ("221".equals(code)){ // 返回码 235 表示认证成功
                    break;
                }
            }
        } finally {socket.close();
        }
        return authed;
    }
  1. 问题背景
    这是一段几年前的代码(作者已经不在公司了),对应的系统一直使用好几年也没发现有啥问题,2024-04-09 下午突然好多人反馈好几个系统无法登录(使用的是腾讯企业邮箱和对应的邮箱密码),提示账号密码错误。经过我着急忙慌的排查,最终把问题定位到这段通过 smtp 进行企业邮箱认证的逻辑上。

  2. 问题现象
    由于系统没有发现任何日志和报错信息,在本地跑起来系统后(庆幸这个系统没有其他的系统依赖,直接运行很简单),通过 debug 发现,正常登录的情况下可以获取到所有发送指令对应的响应信息。但是,异常的时候,在 while 循环中,只遍历到前 2 条指令对应的响应信息,加上建立连接时的初始响应信息,总计只有 3 条响应信息。

  3. 原因猜测
    由于我工作中几乎接触不到 IO 流相关的内容,所以这个猜测很可能不正确。
    我猜测的是代码在发送完所有指令后,在只响应了 3 条信息后,while 循环就已经运行结束了,不过现在想好像也不太可能,因为问题虽然是偶现的,但是每次出现,必定是只响应 3 条然后 reader.readLine() 就读取到 null 从而结束循环了。

  4. 临时修复
    由于是线上问题比较紧急,再结合我之前的猜测,我当时采用了最 low 的处理方式,通过 Threed.sleep(500) 进行等待,然后问题修复上线了。

  5. 彻底修复
    以前不知道 smtp 可以这样通过指令进行邮箱的操作,在了解后,找了机器在终端里通过 telnet 的方式手动进行了代码中的操作,尝试了无数遍没有得到任何的重现,哪怕是直接在出问题的网络环境中。
    基于这个终端内的体验,我想到了代码中可能导致前几条指令并没有真正发出,最终 flush 才真正一起发出的可能,所以我把代码优化成严格的串行操作,每发出一条指令就进行 flush 并接收其响应,根据响应再发出下一条指令。经过自测,问题完全修复。
    个人认为这样更加标准,因为完全模拟了手动在终端里的操作流程。

  6. 疑惑
    7.1 原作者的代码逻辑是哪里有什么隐患吗?我最终的处理是否标准合理?
    7.2 为什么好几年没有更改的情况下,突然出现这个问题?我们机房或者腾讯企业邮箱机房网络严重波动?
    7.3 关于 Threed.sleep(500) 临时修复有个奇怪的现象,在 flush 前进行 Threed.sleep(500) 可以修复问题,但是在 flush 后进行 Threed.sleep(500) 却不能修复,这是为什么?

感谢有懂的大佬进行指点

正文完
 0