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;
}
问题背景 这是一段几年前的代码(作者已经不在公司了),对应的系统一直使用好几年也没发现有啥问题,2024-04-09 下午突然好多人反馈好几个系统无法登录(使用的是腾讯企业邮箱和对应的邮箱密码),提示账号密码错误。经过我着急忙慌的排查,最终把问题定位到这段通过 smtp 进行企业邮箱认证的逻辑上。
问题现象 由于系统没有发现任何日志和报错信息,在本地跑起来系统后(庆幸这个系统没有其他的系统依赖,直接运行很简单),通过 debug 发现,正常登录的情况下可以获取到所有发送指令对应的响应信息。但是,异常的时候,在 while 循环中,只遍历到前 2 条指令对应的响应信息,加上建立连接时的初始响应信息,总计只有 3 条响应信息。
原因猜测 由于我工作中几乎接触不到 IO 流相关的内容,所以这个猜测很可能不正确。 我猜测的是代码在发送完所有指令后,在只响应了 3 条信息后,while 循环就已经运行结束了,不过现在想好像也不太可能,因为问题虽然是偶现的,但是每次出现,必定是只响应 3 条然后 reader.readLine() 就读取到 null 从而结束循环了。
临时修复 由于是线上问题比较紧急,再结合我之前的猜测,我当时采用了最 low 的处理方式,通过 Threed.sleep(500) 进行等待,然后问题修复上线了。
彻底修复 以前不知道 smtp 可以这样通过指令进行邮箱的操作,在了解后,找了机器在终端里通过 telnet 的方式手动进行了代码中的操作,尝试了无数遍没有得到任何的重现,哪怕是直接在出问题的网络环境中。 基于这个终端内的体验,我想到了代码中可能导致前几条指令并没有真正发出,最终 flush 才真正一起发出的可能,所以我把代码优化成严格的串行操作,每发出一条指令就进行 flush 并接收其响应,根据响应再发出下一条指令。经过自测,问题完全修复。 个人认为这样更加标准,因为完全模拟了手动在终端里的操作流程。
疑惑 7.1 原作者的代码逻辑是哪里有什么隐患吗?我最终的处理是否标准合理? 7.2 为什么好几年没有更改的情况下,突然出现这个问题?我们机房或者腾讯企业邮箱机房网络严重波动? 7.3 关于 Threed.sleep(500) 临时修复有个奇怪的现象,在 flush 前进行 Threed.sleep(500) 可以修复问题,但是在 flush 后进行 Threed.sleep(500) 却不能修复,这是为什么?
感谢有懂的大佬进行指点
1
ren930480304 OP 才发现排版被 v 站调整了一些,辛苦大家将就看看
|
2
samuexl 233 天前
看看 nginx 有没有配置变动,缓存开关之类的修改,或者 dns 缓存
|
3
ren930480304 OP @samuexl 没有,我们这次开发环境、测试环境、生产环境总计 4 个环境,在不同的地点和网络环境下,同时出现相同的这个问题
|
4
xiri 233 天前 via Android
@ren930480304 有没有可能是腾讯企业邮改了啥
|
5
ren930480304 OP @xiri 我也在猜测有这个可能,因为最新发现,公司的另一个系统,是使用的 python 写的,也是调用腾讯企业邮箱进行登录,也出现一模一样的问题了。不过腾讯企业邮,找不到客服及迭代明细......
|
6
iminto 233 天前
既然你 flush 一下就修复了,邮件认证可能没啥变动,更可能是网络的某些配置变了。
比如 MTU 或者 tcp_ip 的一些配置被改了 |
7
luozic 233 天前
去看看腾讯企业邮的文档 or 咨询一下他们,是不是有啥变更?
|
8
iyiluo 233 天前
可以看看 java 开源的邮箱协议代码是怎么认证的,通常这些协议都有一套标准规范,只有那些非常熟悉规则的开发者才知道里面有哪些坑
|
9
bigfei 233 天前 via Android
有可能腾讯最新限制了每秒接收的指令数量,一下子发送 4 条不符合要求。直接咨询企业邮客服问问看
|
10
ren930480304 OP @luozic 腾讯企业邮的开发者文档目前只看到了关于 http 接口的描述
|
11
ren930480304 OP @bigfei 目前别的语言实现的这段逻辑也发现了相同的问题,更倾向于腾讯是不是改了啥,想办法找找客服
|
12
ren930480304 OP @iyiluo 感谢指点,抽空看看能不能学习一下
|
13
zed1018 233 天前
牛皮啊,硬核操作 smtp 协议。
|
14
yippees 233 天前
这代码本身就不符合协议。可能 tx 改好了。
可以试一试 telnet 下模拟也把 5 条合成一条直接发送 ,看看 tx 返回什么。 |
15
F7TsdQL45E0jmoiG 233 天前
代码不严谨,应该是串行发送 smtp 命令,并且每次都按标准检查返回响应码
|
16
ren930480304 OP @morenacl 个人也是这么认为的,所以当时按照这个思路优化后问题就解决了
|
17
ren930480304 OP @yippees 模拟过,不过没有复现,多条指令拼接在一起,不管是换行符还是回车字符,从返回的响应码来看感觉只识别了第一条指令
|
19
julyclyde 232 天前
smtp 是交互式协议,你这么直接发一堆出去肯定是不对的
偶尔服务器能成功接受,那是服务器写的不对 |
20
ren930480304 OP @julyclyde 感谢科普,看来我后边修复的方式应该就没啥问题了
|