前言
最近拿到一个需求,需要给一个基于jdk1.6开发的系统增加登录验证码,这个过程中需要调用第三方接口,如果第三方接口调用失败,则需发送告警邮件到指定邮箱。比较坑的是,因为系统比较老,加了 jar 包之后一直不生效,所以不能引入其他依赖,比如 mail-api 之类的。
基本步骤
只基于一些工具包做过邮件发送,趁此机会了解了一下 SMTP 协议。
使用 SMTP 协议发送邮件分为几个步骤:
- 与 SMTP 服务器建立连接
- 身份认证(用户名和密码需通过 base64 进行编码,一般不是邮箱密码,而是一个一次性密码)
- 指定收件人(没有抄送人选项,抄送通过多次指定收件人实现)
- 邮件内容
- 退出
基于命令行
# 使用 telnet 连接到 smtp 服务器
> telnet smtp.163.com 25
Trying 240e:938:a07:6:0:14:203:45...
Connected to smtp163.mail.ntes53.netease.com.
Escape character is '^]'.
220 163.com Anti-spam GT for Coremail System (163com[20141201])
# HELO 命令
> HELO stmp.163.com
250 OK
# 认证 (dXNlcm5hbWU6 即为 username:、UGFzc3dvcmQ6 即为 password:)
> AUTH LOGIN
334 dXNlcm5hbWU6
# 输入 base64 编码后的用户名
> emh1bG9uZ2t1bjIwQDE2My5jb
334 UGFzc3dvcmQ6
# 输入 base64 编码的密码
> WU54UktjYVJLZFhLWmV
235 Authentication successful
到这里已经成功登录到服务器了。
# 发件人
> MAIL FROM:<[email protected]>
250 Mail OK
# 收件人
> RCPT TO:<[email protected]>
250 Mail OK
# 抄送人
> RCPT TO:<[email protected]>
250 Mail OK
邮件内容命令为 DATA,然后以 . 作为结束。
> DATA
354 End data with <CR><LF>.<CR><LF>
> Subject: Greet Email
> This is a greet email from 163!
> .
250 Mail OK queued as gzsmtp2,PSgvCgBnBIxyor+sPBA--.4614S2 1751466502 # 已经进入发送队列
# 使用 QUIT 命令退出
> QUIT
221 Bye
Connection closed by foreign host.
以上就是使用命令行发送邮件的全过程,其中要注意,短时间内发送多封邮件,可能会被反垃圾邮件程序拦截掉,邮件标题、内容最好不要太随意。
纯 Java 实现
基于 socket 和 IO 流实现邮件发送,代码如下:
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class MyMail {
public static void sendMail(String host, int port, String username, String password,
String receiver, String ccReceiver, String subject, String body) throws IOException {
Socket socket = new Socket();
InetSocketAddress inetSocketAddress = new InetSocketAddress(host, port);
socket.connect(inetSocketAddress);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
readInputStream(inputStream);
// 发送 HELO 命令
sendCommand(outputStream, "HELO " + host);
readInputStream(inputStream);
// 发送 AUTH LOGIN 命令
sendCommand(outputStream, "AUTH LOGIN");
readInputStream(inputStream);
sendCommand(outputStream, base64Encode(username));
readInputStream(inputStream);
sendCommand(outputStream, base64Encode(password));
readInputStream(inputStream);
sendCommand(outputStream, "MAIL FROM:<" + username + ">");
readInputStream(inputStream);
sendCommand(outputStream, "RCPT TO:<" + receiver + ">");
readInputStream(inputStream);
sendCommand(outputStream, "RCPT TO:<" + ccReceiver + ">");
readInputStream(inputStream);
sendCommand(outputStream, "DATA");
readInputStream(inputStream);
sendCommand(outputStream, "Subject: " + subject);
sendCommand(outputStream, body);
sendCommand(outputStream, ".");
readInputStream(inputStream);
sendCommand(outputStream, "QUIT");
readInputStream(inputStream);
}
public static String base64Encode(String text) {
return Base64.getEncoder().encodeToString(text.getBytes());
}
private static void readInputStream(InputStream inputStream) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
StringBuilder response = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
response.append(line).append("\n");
if (line.length() >= 3 && line.charAt(3) == ' ') {
break;
}
}
System.out.println(response.toString().trim());
}
private static void sendCommand(OutputStream outputStream, String command) throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
bufferedWriter.write(command + "\r\n");
bufferedWriter.flush();
}
public static void main(String[] args) {
try {
sendMail("smtp.163.com", 25,
"[email protected]", "YNxRKcadfsdXsdfa",
"[email protected]", "[email protected]",
"Greet day", "Today is a great day!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("邮件发送失败");
}
}
}
注意,认证的密码不是邮箱的密码,而是一次性授权码,在邮箱设置里开启“POP3/SMTP服务”可以获取到。