发送邮件
要在网络中实现邮件功能,必须有专门的邮件服务器,相当于现实生活中的邮局。邮件服务器负责接收用户投递过来的邮件,然后将邮件投递给邮件接收人的电子邮箱中。在Java中发送电子邮件主要涉及到两种方式:直接使用SMTP(Simple Mail Transfer Protocol)协议和JavaMail API。SMTP是一个应用层协议,用于在互联网上传输电子邮件。而JavaMail API则是Java的一个扩展,提供了一套高级接口来发送和接收电子邮件,它封装了底层的协议细节。
1.SMTP协议
直接使用SMTP协议意味着你需要手动处理所有与SMTP服务器的交互,包括连接、认证、发送邮件数据以及断开连接。这种方式通常涉及Socket编程,你需要了解SMTP命令集,例如HELO/EHLO、AUTH、MAIL FROM、RCPT TO、DATA等。
优点:
对底层协议有更深入的理解和控制。
可以实现定制化的邮件发送逻辑。
缺点:
编程复杂度高,需要处理各种错误情况和协议细节。
安全性问题,如TLS/SSL的使用,需要额外处理。
需要处理邮件的编码、MIME类型等问题。
下面说一下SMTP的工作流程
- 建立连接:邮件客户端通过TCP协议连接到SMTP服务器的默认端口(通常是25)。如果使用SSL/TLS,则会连接到465或587端口。
- 服务器欢迎消息:连接建立后,SMTP服务器会发送一条欢迎消息,通常是“220 server_name ESMTP”。
- 客户端发出HELO或EHLO命令:客户端通过发送“HELO”或“EHLO”命令来标识自己。
- 身份验证:在邮件系统中,邮件客户端通常需要通过某种身份验证机制进行身份验证。
- MAIL FROM命令:客户端发送“MAIL FROM:”命令,指定邮件的发件人。
- RCPT TO命令:客户端发送“RCPT TO:”命令,指定邮件的一个或多个收件人。
- DATA命令:一旦收件人被接受,客户端发送“DATA”命令,随后是邮件的实际内容。邮件内容以点号(.)结尾。
- 发送邮件内容:客户端发送邮件的头部和正文,直到以单独的一行点号结束。
- 服务器响应:服务器会发送响应代码,如“250 OK”,表示邮件已被接受。
- QUIT命令:发送完邮件后,客户端发送“QUIT”命令来结束会话。
import javax.net.ssl.SSLSocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class SendMailNative {
public static void main(String[] args) {
// SMTP服务地址
String smtpHost = "smtp.163.com";
// SMTP服务端口
int smtpPort = 587;
// 邮箱地址
String username = "13512344321@163.com";
// 邮箱密码
String password = "VFMJGJGJKHFYYYU";
// 发件人邮箱
String sender = "13512344321@163.com";
// 收件人邮箱
String recipient = "741233216@qq.com";
// 邮件内容
String subject = "Test Subject";
String body = "This is the email body.";
FlexibleTrustSSLSocketFactory factory = new FlexibleTrustSSLSocketFactory(FlexibleTrustSSLSocketFactory.TrustMode.TRUST_ALL);
PrintWriter out = null;
BufferedReader in = null;
SSLSocket sslSocket = null;
try {
// 连接到SMTP服务器
sslSocket = (SSLSocket) factory.createSocket(smtpHost, smtpPort);
out = new PrintWriter(sslSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()))
// 读取SMTP服务器的欢迎信息
String response = in.readLine();
System.out.println("创建连接响应:" + response);
if (!response.startsWith("220")) {
throw new RuntimeException("Unexpected response from server: " + response);
}
out.println("EHLO " + smtpHost);
response = in.readLine();
System.out.println("EHLO响应:" + response);
if (!response.startsWith("250")) {
throw new RuntimeException("Unexpected response from server: " + response);
}
// 启动TLS加密(如果服务器支持)
out.println("STARTTLS");
response = in.readLine();
System.out.println("STARTTLS响应:" + response);
// 使用PLAIN认证
String authStr = "\u0000" + username + "\u0000" + password;
out.println("AUTH PLAIN " + Base64.getEncoder().encodeToString(authStr.getBytes(StandardCharsets.UTF_8)));
out.println("MAIL FROM:<" + sender + ">");
out.println("RCPT TO:<" + recipient + ">");
// 发送邮件内容
out.println("DATA");
out.println("Subject: " + subject);
out.println("");
out.println(body);
out.println(".");
// 结束SMTP会话
out.println("QUIT");
StringBuilder responseBuilder = new StringBuilder();
String line = null;
// 使用循环不断读取下一行,直到没有更多行可读
while ((line = in.readLine()) != null) {
// 将读取的行添加到StringBuilder中,每行之间添加换行符
responseBuilder.append(line).append(System.lineSeparator());
}
String responseNew = responseBuilder.toString().trim();
System.out.println("邮件响应结果:" + responseNew);
if (!responseNew.startsWith("250")) {
throw new RuntimeException("Unexpected response from server: " + responseNew);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 关闭连接
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (sslSocket != null) {
sslSocket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
FlexibleTrustSSLSocketFactory类用于指定哪些主机的 SSL/TLS 证书会被无条件信任,即使这些证书是由不受信任的证书颁发机构(CA)签发的,或者是自签名证书。这在测试环境中非常有用,但不推荐在生产环境中使用,因为它会降低安全性。默认情况下,JavaMail 会验证 SSL/TLS 证书的完整信任链,确保它们是由受信任的 CA 签发的,并且在证书的生命周期内是有效的。如果证书不能被信任,JavaMail 会抛出一个 SSL 握手异常。
import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class FlexibleTrustSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
private final TrustManager trustManager;
public FlexibleTrustSSLSocketFactory(TrustMode mode, String... trustedHosts) {
try {
SSLContext context = SSLContext.getInstance("TLS");
switch (mode) {
case TRUST_ALL:
trustManager = new TrustAllManager();
break;
case TRUST_SPECIFIC_HOSTS:
trustManager = new TrustSpecificHostsManager(trustedHosts);
break;
case NO_TRUST:
default:
trustManager = new NoTrustManager();
break;
}
context.init(null, new TrustManager[]{trustManager}, null);
delegate = context.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException("Failed to initialize SSLContext", e);
}
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return wrapSocket(host, delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return wrapSocket(host, delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return wrapSocket(host.getHostAddress(), delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return wrapSocket(address.getHostAddress(), delegate.createSocket(address, port, localAddress, localPort));
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return wrapSocket(host, delegate.createSocket(s, host, port, autoClose));
}
private Socket wrapSocket(String host, Socket socket) {
if (socket instanceof SSLSocket) {
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"}); // Set preferred protocols
}
return socket;
}
private static class TrustAllManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
private static class TrustSpecificHostsManager implements X509TrustManager {
private final Set trustedHosts = new HashSet<>();
public TrustSpecificHostsManager(String... trustedHosts) {
this.trustedHosts.addAll(Arrays.asList(trustedHosts));
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
if (!trustedHosts.contains(chain[0].getSubjectDN().getName())) {
throw new IllegalArgumentException("Untrusted host certificate.");
}
}
@Override
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
private static class NoTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
throw new IllegalArgumentException("Client certificate not trusted.");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
throw new IllegalArgumentException("Server certificate not trusted.");
}
@Override
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}
public enum TrustMode {
TRUST_ALL(1, "信任所有证书"),
TRUST_SPECIFIC_HOSTS(2, "信任列表中的主机证书"),
NO_TRUST(3, "不信任任何证书");
private Integer code;
private String msg;
TrustMode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}
响应结果
创建连接响应:220 163.com Anti-spam GT for Coremail System (163com[20141201])
EHLO响应:250-mail
STARTTLS响应:250-PIPELINING
邮件响应结果:250-AUTH LOGIN PLAIN XOAUTH2
250-AUTH=LOGIN PLAIN XOAUTH2
250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UF2fSh7UCa0xDrUUUUj
250-STARTTLS
250-ID
250 8BITMIME
454 Command not permitted when TLS active
235 Authentication successful
250 Mail OK
250 Mail OK
354 End data with .
250 Mail OK queued as gzga-smtp-mta-g3-4,_____wD3v8+3dIZmP4qSBg--.24498S2 1720087736
221 Bye
2.JavaMail API
JavaMail API是一个Java SE和Java EE平台的标准扩展,它提供了发送和接收邮件的高级接口,包括对SMTP、POP3、IMAP等协议的支持。JavaMail API封装了底层协议的复杂性,使得开发者可以更专注于业务逻辑而不是协议细节。
优点:
简化了邮件发送和接收的代码,提高了开发效率。
提供了错误处理和重试机制。
支持多种邮件协议和高级特性,如附件、HTML邮件、多部分邮件等。
支持TLS/SSL安全连接,提高了邮件通信的安全性。
缺点:
相对于直接使用SMTP,它引入了一定的性能开销,因为有额外的抽象层。
需要额外的库文件(JavaMail API和JAF),增加了项目的依赖。
使用JavaMail发送邮件分三步:
- 配置Properties:Properties对象中设置SMTP主机、端口、认证方式,并开启SSL支持。
- 创建Session:使用上述Properties对象创建Session实例,并提供一个Authenticator对象用于处理SMTP认证。
- 创建并发送邮件:创建MimeMessage实例,设置收件人、主题、正文等,然后使用Transport发送邮件。
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
public class SendEmailSSL {
public static void main(String[] args) {
// SMTP服务地址
final String smtpHost = "smtp.163.com";
// SMTP服务端口
final String smtpPort = "25";
// 邮箱地址
final String username = "13512344321@163.com";
// 邮箱密码
final String password = "VFMJJIUIGUGFYR";
// 发件人邮箱
final String sender = "13512344321@163.com";
// 收件人邮箱
final String recipient = "741233216@qq.com";
// 配置邮件服务器
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
// props.put("mail.smtp.ssl.protocols", "TLSv1.3");
// SMTP服务器地址
props.put("mail.smtp.host", smtpHost);
// 端口号
props.put("mail.smtp.port", smtpPort);
// ssl配置
// props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
// props.put("mail.smtp.socketFactory.fallback", "false");
// 不允许非SSL连接
// props.put("mail.smtp.ssl.enable", "true");
// 添加服务器证书信任
// props.put("mail.smtp.ssl.trust", "*");
// 开启调试模式
// props.put("mail.debug", "true");
// 创建Session
Session session = Session.getInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
try {
// 创建MimeMessage
Message message = new MimeMessage(session);
// 发件人邮箱
message.setFrom(new InternetAddress(sender));
// 收件人邮箱
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(recipient));
message.setSubject("测试邮件主题");
message.setText("测试邮件正文");
// 发送邮件
Transport.send(message);
System.out.println("邮件发送成功");
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
}
3.其他
发邮件还需要注意下加密协议,TLS(Transport Layer Security)和SSL(Secure Sockets Layer)都是用于在网络上安全传输数据的协议,尤其在发送电子邮件时,它们用于保护邮件内容免受窃听或篡改。尽管它们有相似的目标,但TLS和SSL之间存在一些关键的区别
1. 历史背景:
● SSL 是由Netscape公司于1994年首次提出,目的是为了保护Web通讯的安全。
● TLS 是SSL的后续版本,由IETF(Internet Engineering Task Force)在1999年提出,基于SSL 3.0进行改进和标准化。
2. 安全性:
● TLS通常被认为比SSL更安全,因为它修复了许多SSL中存在的漏洞,并引入了更强的加密算法。
● TLS还支持更安全的密钥交换算法,如Diffie-Hellman。
3. 版本:
● SSL有三个主要版本:SSL 2.0、SSL 3.0。
● TLS有多个版本:TLS 1.0、TLS 1.1、TLS 1.2、TLS 1.3,其中TLS 1.3是最新的版本,提供了最先进的安全特性。
4. 端口使用:
● SSL通常使用端口443(HTTPS)或端口465(SMTPS)。
● TLS可以使用与SSL相同的端口,但更多时候与STARTTLS一起使用,先建立一个非加密连接,然后升级到加密连接,通常使用端口587。
综合来说,当选择使用TLS还是SSL时,由于TLS提供了更强的安全性并且修复了SSL中发现的许多安全漏洞,TLS通常是更好的选择。在实际应用中,大多数现代邮件服务都推荐使用TLS,并且可能不支持较旧的SSL版本。因此,除非有特定的要求,否则应优先使用TLS。
- 本文标签: Java
- 本文链接: https://lanzi.cyou/article/7
- 版权声明: 本文由咖啡豆原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权