您的当前位置:首页正文

工作记录 如何发送邮件,并实现证书校验

2024-11-07 来源:个人技术集锦

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[] {});
    }
}

显示全文