1、引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
</dependency>
2、设置邮件内容
// 根据参数生成邮件
MultiPartEmail email = new MultiPartEmail();
email.setHostName(remoteNotifyServer.getServer());
email.setSmtpPort(Integer.parseInt(remoteNotifyServer.getPort()));
setEmailAuth(remoteNotifyServer, email);
email.setFrom(remoteNotifyServer.getEmailFrom());
email.setCharset(EMAIL_CHARSET);
email.setSubject(title);
email.setMsg(content);
email.setSocketTimeout(LegoNumberConstant.THIRTY * LegoNumberConstant.THOUSAND);
List<InternetAddress> internetAddresses = Collections.singletonList(new InternetAddress(receiver));
email.setTo(internetAddresses);
email.setSocketTimeout(IsmNumberConstant.THOUSAND * IsmNumberConstant.FIVE);
email.setSocketConnectionTimeout(IsmNumberConstant.THOUSAND * IsmNumberConstant.FIVE);
// 设置代理服务器的系统变量
Properties props = setProperty(remoteNotifyServer, email);
initMailcapCommandMap();
Properties properties = email.getMailSession().getProperties();
properties.putAll(props);
email.send();
private Properties setProperty(RemoteNotifyServer remoteNotifyServer, MultiPartEmail email) {
Properties properties = new Properties();
// 使用代理发送
if (remoteNotifyServer.isProxyEnable()) {
properties.setProperty(PROXY_SET, TRUE);
properties.setProperty(SOCKS_PROXY_HOST, remoteNotifyServer.getProxyServer());
properties.setProperty(SOCKS_PROXY_PORT, String.valueOf(remoteNotifyServer.getProxyPort()));
} else {
// 不使用代理
properties.setProperty(PROXY_SET, FALSE);
properties.setProperty(SOCKS_PROXY_HOST, "");
properties.setProperty(SOCKS_PROXY_PORT, "");
}
// 如果没有添加证书,则可以直接进行非加密发送邮件,如果添加了相应的证书,则需要走加密通道
if (remoteNotifyServer.getIsSslEnable() != null && remoteNotifyServer.getIsSslEnable()) { // 启用SSL
email.setSSLOnConnect(Boolean.TRUE);
email.setSslSmtpPort(remoteNotifyServer.getSslSmtpPort());
email.setSSLCheckServerIdentity(true);
properties.setProperty(EmailConstants.MAIL_HOST, remoteNotifyServer.getServer());
properties.setProperty(EmailConstants.MAIL_PORT, remoteNotifyServer.getSslSmtpPort());
properties.setProperty(EmailConstants.MAIL_SMTP_SSL_ENABLE, "true");
properties.setProperty(EmailConstants.MAIL_SMTP_AUTH, "true");
properties.setProperty(SmtpSslSocketFactory.SMTP_IP_ADDRESS, remoteNotifyServer.getServer());
properties.setProperty(SmtpSslSocketFactory.SMTP_SSL_CONTEXT, SmtpSslSocketFactory.SSL_CONTEXT);
properties.put("mail.smtp.ssl.socketFactory", new SmtpSslSocketFactory(properties));
properties.setProperty("mail.smtp.ssl.socketFactory.fallback", FALSE);
properties.setProperty(EmailConstants.MAIL_SMTP_SSL_SOCKET_FACTORY_PORT,
remoteNotifyServer.getSslSmtpPort());
}
if (remoteNotifyServer.getIsTlsEnable() != null && remoteNotifyServer.getIsTlsEnable()) {
// 启用TLS
email.setStartTLSEnabled(Boolean.TRUE);
email.setStartTLSRequired(Boolean.TRUE);
properties.setProperty(EmailConstants.MAIL_SMTP_AUTH, "true");
properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE, "true");
properties.setProperty(SmtpSslSocketFactory.SMTP_IP_ADDRESS, remoteNotifyServer.getServer());
properties.setProperty(SmtpSslSocketFactory.SMTP_SSL_CONTEXT, SmtpSslSocketFactory.TLS_CONTEXT);
properties.put("mail.smtp.ssl.socketFactory", new SmtpSslSocketFactory(properties));
properties.setProperty("mail.smtp.ssl.socketFactory.fallback", FALSE);
}
// 设置接收超时时间
properties.setProperty(MAIL_SNMTP_TIME_OUT, TIME_OUT);
// 设置读取超时时间
properties.setProperty(MAIL_SMTP_CONNECT_TIME_OUT, TIME_OUT);
// 设置写入超时时间
properties.setProperty(MAIL_SMTP_WRITE_TIME_OUT, TIME_OUT);
return properties;
}
private void initMailcapCommandMap() {
MailcapCommandMap mailcapCommandMap = new MailcapCommandMap();
if (CommandMap.getDefaultCommandMap() instanceof MailcapCommandMap) {
mailcapCommandMap = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
}
mailcapCommandMap.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mailcapCommandMap.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mailcapCommandMap.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mailcapCommandMap.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
mailcapCommandMap.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mailcapCommandMap);
}
public abstract class BcmSslSocketFactory extends SSLSocketFactory {
/**
* IP地址
*/
private String ip;
/**
* 安全协议
*/
private String[] protocols;
/**
* 使用指定的TrustManager构造SSLSocketFactory
*
* @param ipAddress ip地址
* @param protocols 建立连接时使用的使用加密协议,如果为空,则默认使用"TLSv1.1","TLSv1.2""TLSv1.3"协议尝试连接。
*/
public BcmSslSocketFactory(String ipAddress, String... protocols) {
if (VerifyUtil.isEmpty(ipAddress)) {
log.error("ipAddress is null.");
throw new LegoCheckedException(ErrorCodeConstant.SSL_INIT_OR_CONNECT_FAIL);
}
this.ip = ipAddress;
this.protocols = VerifyUtil.isEmpty(protocols) ? new String[] {"TLSv1.1", "TLSv1.2", "TLSv1.3"} : protocols;
}
/**
* 创建一个未连接的套接字。
*
* @return Socket 套接字
* @throws IOException 如果不能创建套接字
*/
@Override
public Socket createSocket() throws IOException {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
Socket socket = sslSocketFactory.createSocket();
if (socket instanceof SSLSocket) {
ProtocolManager.resetEnabledProtocolsAndCipherSuites((SSLSocket) socket, protocols);
}
return socket;
}
/**
* 返回在连接到指定主机的给定端口的现有套接字上分层的套接字。
*
* @param socket 已有的套接字
* @param host 服务器主机
* @param port 服务器断开
* @param autoClose 关闭此套接字时关闭底层套接字
* @return 连接到指定主机和端口的套接字
* @throws IOException 如果创建套接字时出现 I/O 错误
*/
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
Socket sslSocket = sslSocketFactory.createSocket(socket, host, port, autoClose);
if (sslSocket instanceof SSLSocket) {
ProtocolManager.resetEnabledProtocolsAndCipherSuites((SSLSocket) sslSocket, protocols);
}
return sslSocket;
}
/**
* 创建一个套接字并把它连接到指定远程主机上的指定远程端口。
*
* @param host 服务器主机
* @param port 服务器端口
* @return Socket 套接字
* @throws IOException 如果创建套接字时出现 I/O 错误
*/
@Override
public Socket createSocket(String host, int port) throws IOException {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
Socket socket = sslSocketFactory.createSocket(host, port);
if (socket instanceof SSLSocket) {
ProtocolManager.resetEnabledProtocolsAndCipherSuites((SSLSocket) socket, protocols);
}
return socket;
}
/**
* 创建一个套接字并把它连接到指定地址上的指定端口号。
*
* @param host 服务器主机
* @param port 服务器端口
* @return Socket 套接字
* @throws IOException 如果创建套接字时出现 I/O 错误
*/
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
Socket socket = sslSocketFactory.createSocket(host, port);
if (socket instanceof SSLSocket) {
ProtocolManager.resetEnabledProtocolsAndCipherSuites((SSLSocket) socket, protocols);
}
return socket;
}
/**
* 创建一个套接字并把它连接到指定远程主机上的指定远程端口。套接字还会绑定到提供的本地地址和端口。
*
* @param host 服务器主机
* @param port 服务器端口
* @param localHost 套接字绑定到的本地地址
* @param localPort 套接字绑定到的本地端口
* @return Socket 套接字
* @throws IOException 如果创建套接字时出现 I/O 错误
*/
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
Socket socket = sslSocketFactory.createSocket(host, port, localHost, localPort);
if (socket instanceof SSLSocket) {
ProtocolManager.resetEnabledProtocolsAndCipherSuites((SSLSocket) socket, protocols);
}
return socket;
}
/**
* 创建一个套接字并把它连接到指定远程端口上的指定远程地址。套接字还会绑定到提供的本地地址和端口。使用为此工厂建立的套接字选项来配置此套接字。
*
* @param address 服务器网络地址
* @param port 服务器端口
* @param localAddr 客户端网络地址
* @param localPort 客户端端口
* @return Socket 套接字
* @throws IOException 如果创建套接字时出现 I/O 错误
*/
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddr, int localPort) throws IOException {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
Socket socket = sslSocketFactory.createSocket(address, port, localAddr, localPort);
if (socket instanceof SSLSocket) {
ProtocolManager.resetEnabledProtocolsAndCipherSuites((SSLSocket) socket, protocols);
}
return socket;
}
/**
* 返回默认情况下启用的密码套件的列表。
*
* @return 默认情况下启用的密码套件的数组。
*/
@Override
public String[] getDefaultCipherSuites() {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
return sslSocketFactory.getDefaultCipherSuites();
}
/**
* 返回可以在 SSL 连接上启用的密码套件的名称。
*
* @return 密码套件名称的数组
*/
@Override
public String[] getSupportedCipherSuites() {
SSLSocketFactory sslSocketFactory = createSslSocketFactory();
return sslSocketFactory.getSupportedCipherSuites();
}
/**
* 创建SSLSocketFactory
*
* @return SSLSocket工厂类
*/
public abstract SSLSocketFactory createSslSocketFactory();
}
public class SmtpSslSocketFactory extends BcmSslSocketFactory {
/**
* smtp 调用的IP地址
*/
public static final String SMTP_IP_ADDRESS = "smtpIpAddress";
/**
* smtp加密方法
*/
public static final String SMTP_SSL_CONTEXT = "smtpSSLContext";
/**
* SSL类型
*/
public static final String SSL_CONTEXT = "SSL";
/**
* TLS类型
*/
public static final String TLS_CONTEXT = "TLS";
private Properties props;
/**
* 构造方法,给父类的构造方法提供参数,支持TLSv1.1, TLSv1.2 TLSv1.3连接
*
* @param props Ip地址
*/
public SmtpSslSocketFactory(Properties props) {
super(props.getProperty(SMTP_IP_ADDRESS), "SSLv3", "SSLv2Hello", "TLSv1.2", "TLSv1.1", "TLSv1");
this.props = props;
}
/**
* 创建SSLSocketFactory
*
* @return SSLSocketFactory SSLSocketFactory
*/
@Override
public SSLSocketFactory createSslSocketFactory() {
SSLSocketFactory sslSocketFactory;
try {
String sslContext = props.getProperty(SmtpSslSocketFactory.SMTP_SSL_CONTEXT);
assert !StringUtils.isEmpty(sslContext);
SSLContext context = SSLContext.getInstance(sslContext);
final String ipAddress = props.getProperty(SMTP_IP_ADDRESS);
context.init(null, new TrustManager[] {new BcmX509TrustManager(ipAddress, new AlarmInformTrustHandler())},
new SecureRandom());
sslSocketFactory = context.getSocketFactory();
} catch (Exception e) {
log.error("Creating SSLSocketFactory failed, ipAddress= {}.", props.getProperty(SMTP_IP_ADDRESS), e);
throw new LegoCheckedException(ErrorCodeConstant.SSL_INIT_OR_CONNECT_FAIL,
"Initializing BcmSslSocketFactory failed.", e);
}
return sslSocketFactory;
}
}
public class AlarmInformTrustHandler implements X509TrustHandler {
@Override
public void handle(Exception exception) throws CertificateException {
if (exception == null) {
return;
}
// 证书校验失败的话,就抛出
if (exception instanceof CertificateException) {
throw (CertificateException)exception;
}
log.error("Unexpected exception happen in certificate verify.", exception);
}
}
public class ProtocolComparator implements Comparator<String> {
private static final String PROTOCOL_TLS = "TLS";
private static final String PROTOCOL_SSL = "SSL";
private static final String PROTOCOL_SPLITTER = "v";
/**
* 对协议按照类型和版本进行比较
*
* @param protocol1 协议1
* @param protocol2 协议2
* @return 根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
*/
@Override
public int compare(String protocol1, String protocol2) {
if (!protocol1.startsWith(PROTOCOL_TLS) && !protocol1.startsWith(PROTOCOL_SSL)) {
return IsmNumberConstant.NEGATIVE_ONE;
}
String[] protocol1Array = protocol1.split(PROTOCOL_SPLITTER);
String[] protocol2Array = protocol2.split(PROTOCOL_SPLITTER);
String protocol1Type = protocol1Array[LegoNumberConstant.ZERO];
double protocol1Version = (protocol1Array.length == LegoNumberConstant.ONE)
? 0.0
: NumberUtil.parseDouble(protocol1Array[1]);
String protocol2Type = protocol2Array[LegoNumberConstant.ZERO];
double protocol2Version = (protocol2Array.length == LegoNumberConstant.ONE)
? 0.0
: NumberUtil.parseDouble(protocol2Array[1]);
int result = compareByProtocolType(protocol1Type, protocol2Type);
if (result != LegoNumberConstant.ZERO) {
return result;
}
return compareByProtocolVersion(protocol1Version, protocol2Version);
}
/**
* 按协议类型进行比较
*
* @param protocol1Type 协议1的类型
* @param protocol2Type 协议2的类型
* @return int 根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
*/
private int compareByProtocolType(String protocol1Type, String protocol2Type) {
if (protocol1Type.equalsIgnoreCase(PROTOCOL_TLS) && protocol2Type.equalsIgnoreCase(PROTOCOL_SSL)) {
return LegoNumberConstant.ONE;
}
if (protocol1Type.equalsIgnoreCase(PROTOCOL_SSL) && protocol2Type.equalsIgnoreCase(PROTOCOL_TLS)) {
return IsmNumberConstant.NEGATIVE_ONE;
}
return LegoNumberConstant.ZERO;
}
/**
* 按协议版本进行比较
*
* @param protocol1Version 协议1的版本
* @param protocol2Version 协议2的版本
* @return int 根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
*/
private int compareByProtocolVersion(double protocol1Version, double protocol2Version) {
return Double.compare(protocol2Version, protocol1Version);
}
}
public class ProtocolManager {
private ProtocolManager() {
}
/**
* 根据指定的安全协议和加密套件重新设置SSLSocket使用的安全协议和加密套件
* 1、若未指定安全协议则采用初始化SSLContext时使用的安全协议类型
* 2、若未指定加密套件则使用默认安全协议对应的加密套件
*
* @param sslSocket SSLSocket
* @param protocols 协议
*/
public static void resetEnabledProtocolsAndCipherSuites(SSLSocket sslSocket, String[] protocols) {
if (sslSocket == null) {
log.error("sslSocket is null.");
return;
}
String[] enabledProtocols = sslSocket.getEnabledProtocols();
log.debug("sslSocket protocol: {},set protocols: {}.", Arrays.toString(enabledProtocols),
Arrays.toString(protocols));
if (protocols != null && protocols.length > 0) {
sslSocket.setEnabledProtocols(protocols);
enabledProtocols = protocols;
}
sslSocket.setEnabledCipherSuites(getCipherSuitesByProtocols(sortProtocolsByVersion(enabledProtocols)));
}
/**
* 返回指定协议集的默认加密套件集
*
* @param protocolsParam 安全协议数组
* @return String[] 默认加密套件集
*/
private static String[] getCipherSuitesByProtocols(String[] protocolsParam) {
Set<String> cipherSuites = new HashSet<>();
for (String protocol : protocolsParam) {
Set<String> suites = getCipherSuitesByProtocol(protocol);
cipherSuites.addAll(suites);
}
return cipherSuites.toArray(new String[] {});
}
/**
* 返回指定协议的默认加密套件集
*
* @param protocol 安全协议
* @return Set<String>
*/
private static Set<String> getCipherSuitesByProtocol(String protocol) {
Set<String> cipherSuites = new HashSet<>();
try {
SSLContext sslcontext = SSLContext.getInstance(protocol);
sslcontext.init(null, null, null);
SSLParameters sslParam = sslcontext.getDefaultSSLParameters();
if (sslParam == null) {
log.error("sslParam is null.");
return cipherSuites;
}
String[] defaultCipherSuites = sslParam.getCipherSuites();
if (defaultCipherSuites == null) {
log.error("defaultCipherSuites is null.");
return cipherSuites;
}
for (String cipherSuite : defaultCipherSuites) {
if (!cipherSuites.contains(cipherSuite)) {
cipherSuites.add(cipherSuite);
}
}
} catch (Exception e) {
log.error("Getting cipher suites failed. Protocol: {}.", protocol, e);
}
return cipherSuites;
}
/**
* 返回根据安全协议类型和版本高低排序后的安全协议数组
*
* @param protocolsParam 安全协议数组
* @return String[] 按安全协议类型和版本高低排序后的安全协议数组
*/
private static String[] sortProtocolsByVersion(String[] protocolsParam) {
List<String> protocolList = Arrays.asList(protocolsParam);
protocolList.sort(new ProtocolComparator());
return protocolList.toArray(new String[] {});
}
}