V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ren930480304
V2EX  ›  Java

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

  •  
  •   ren930480304 · 19 天前 · 914 次点击
    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" + "\r\n");
                writer.write("auth login" + "\r\n");
                writer.write(encodedName + "\r\n");
                writer.write(encodedPassword + "\r\n");
                writer.write("quit" + "\r\n");
                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) 却不能修复,这是为什么?

    感谢有懂的大佬进行指点

    20 条回复    2024-04-17 10:38:00 +08:00
    ren930480304
        1
    ren930480304  
    OP
       19 天前
    才发现排版被 v 站调整了一些,辛苦大家将就看看
    samuexl
        2
    samuexl  
       19 天前
    看看 nginx 有没有配置变动,缓存开关之类的修改,或者 dns 缓存
    ren930480304
        3
    ren930480304  
    OP
       19 天前
    @samuexl 没有,我们这次开发环境、测试环境、生产环境总计 4 个环境,在不同的地点和网络环境下,同时出现相同的这个问题
    xiri
        4
    xiri  
       19 天前 via Android
    @ren930480304 有没有可能是腾讯企业邮改了啥
    ren930480304
        5
    ren930480304  
    OP
       19 天前
    @xiri 我也在猜测有这个可能,因为最新发现,公司的另一个系统,是使用的 python 写的,也是调用腾讯企业邮箱进行登录,也出现一模一样的问题了。不过腾讯企业邮,找不到客服及迭代明细......
    iminto
        6
    iminto  
       19 天前
    既然你 flush 一下就修复了,邮件认证可能没啥变动,更可能是网络的某些配置变了。

    比如 MTU 或者 tcp_ip 的一些配置被改了
    luozic
        7
    luozic  
       18 天前
    去看看腾讯企业邮的文档 or 咨询一下他们,是不是有啥变更?
    iyiluo
        8
    iyiluo  
       18 天前
    可以看看 java 开源的邮箱协议代码是怎么认证的,通常这些协议都有一套标准规范,只有那些非常熟悉规则的开发者才知道里面有哪些坑
    bigfei
        9
    bigfei  
       18 天前 via Android
    有可能腾讯最新限制了每秒接收的指令数量,一下子发送 4 条不符合要求。直接咨询企业邮客服问问看
    ren930480304
        10
    ren930480304  
    OP
       18 天前
    @luozic 腾讯企业邮的开发者文档目前只看到了关于 http 接口的描述
    ren930480304
        11
    ren930480304  
    OP
       18 天前
    @bigfei 目前别的语言实现的这段逻辑也发现了相同的问题,更倾向于腾讯是不是改了啥,想办法找找客服
    ren930480304
        12
    ren930480304  
    OP
       18 天前
    @iyiluo 感谢指点,抽空看看能不能学习一下
    zed1018
        13
    zed1018  
       18 天前
    牛皮啊,硬核操作 smtp 协议。
    yippees
        14
    yippees  
       18 天前
    这代码本身就不符合协议。可能 tx 改好了。
    可以试一试 telnet 下模拟也把 5 条合成一条直接发送 ,看看 tx 返回什么。
    morenacl
        15
    morenacl  
       18 天前
    代码不严谨,应该是串行发送 smtp 命令,并且每次都按标准检查返回响应码
    ren930480304
        16
    ren930480304  
    OP
       18 天前
    @morenacl 个人也是这么认为的,所以当时按照这个思路优化后问题就解决了
    ren930480304
        17
    ren930480304  
    OP
       18 天前
    @yippees 模拟过,不过没有复现,多条指令拼接在一起,不管是换行符还是回车字符,从返回的响应码来看感觉只识别了第一条指令
    julyclyde
        18
    julyclyde  
       17 天前
    @zed1018 操作 smtp 咋就硬核了?这不是很常见吗?
    我初中的时候就这么玩了
    julyclyde
        19
    julyclyde  
       17 天前
    smtp 是交互式协议,你这么直接发一堆出去肯定是不对的
    偶尔服务器能成功接受,那是服务器写的不对
    ren930480304
        20
    ren930480304  
    OP
       13 天前
    @julyclyde 感谢科普,看来我后边修复的方式应该就没啥问题了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4976 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 09:23 · PVG 17:23 · LAX 02:23 · JFK 05:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.