Oracle 19c中JavaMail调用SendGrid未触发STARTTLS的问题求助
问题:Oracle数据库中调用JavaMail发送邮件未触发STARTTLS
环境信息
- Oracle 19c
- Java 1.8.0_451
- JavaMail 1.6.7
数据库中部署的Java类代码
CREATE OR REPLACE AND RESOLVE JAVA SOURCE NAMED SECURITY."SendMail" as import java.util.*; import java.io.*; import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; public class SendMail { static { MailcapCommandMap mailcap = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); mailcap.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain"); mailcap.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html"); mailcap.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed"); mailcap.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822"); CommandMap.setDefaultCommandMap(mailcap); } public static int Send(String smtpServer, String sender, String recipient, String ccRecipient, String bccRecipient, String subject, String body, String[] errorMessage, String attachments, final String username, final String password) { int errorStatus = 0; System.setProperty("javax.net.ssl.trustStore", "/u02/sendgrid/wallet/mytruststore.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "changeit"); System.setProperty("javax.net.ssl.trustStoreType", "JKS"); Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.starttls.required", "true"); props.put("mail.smtp.ssl.protocols", "TLSv1.2"); props.put("mail.smtp.host", smtpServer); props.put("mail.smtp.port", "587"); props.put("mail.debug", "true"); Session session = Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); session.setDebug(true); try { MimeMessage msg = new MimeMessage(session); msg.setFrom(new InternetAddress(sender)); // To InternetAddress[] toAddresses = InternetAddress.parse(recipient); msg.setRecipients(Message.RecipientType.TO, toAddresses); // CC if (ccRecipient != null && !ccRecipient.trim().isEmpty()) { InternetAddress[] ccAddresses = InternetAddress.parse(ccRecipient); msg.setRecipients(Message.RecipientType.CC, ccAddresses); } // BCC if (bccRecipient != null && !bccRecipient.trim().isEmpty()) { InternetAddress[] bccAddresses = InternetAddress.parse(bccRecipient); msg.setRecipients(Message.RecipientType.BCC, bccAddresses); } msg.setSubject(subject, "UTF-8"); msg.setSentDate(new Date()); Multipart mp = new MimeMultipart(); // Body part with HTML or plain text detection MimeBodyPart mbpText = new MimeBodyPart(); if (body == null || body.trim().isEmpty()) { body = "[No message body provided]"; } if (body.toLowerCase().contains("<html") || body.toLowerCase().contains("<body")) { mbpText.setContent(body, "text/html; charset=UTF-8"); } else { mbpText.setContent(body, "text/plain; charset=UTF-8"); } mp.addBodyPart(mbpText); // Attachments if (attachments != null && !attachments.trim().isEmpty()) { int startIndex = 0, posIndex; while ((posIndex = attachments.indexOf("///", startIndex)) != -1) { String filePath = attachments.substring(startIndex, posIndex).trim(); addAttachment(mp, filePath); startIndex = posIndex + 3; } if (startIndex < attachments.length()) { String filePath = attachments.substring(startIndex).trim(); addAttachment(mp, filePath); } } msg.setContent(mp); Transport.send(msg); } catch (Exception e) { errorMessage[0] = e.toString(); errorStatus = 1; } return errorStatus; } private static void addAttachment(Multipart mp, String filePath) throws MessagingException { File file = new File(filePath); if (!file.exists()) return; MimeBodyPart mbp = new MimeBodyPart(); FileDataSource fds = new FileDataSource(file); mbp.setDataHandler(new DataHandler(fds)); mbp.setFileName(fds.getName()); mp.addBodyPart(mbp); } }
调试日志
DEBUG: getProvider() returning javax.mail.Provider[TRANSPORT,smtp,com.sun.mail.smtp.SMTPTransport,Sun Microsystems, Inc] DEBUG SMTP: useEhlo true, useAuth true DEBUG SMTP: useEhlo true, useAuth true DEBUG: SMTPTransport trying to connect to host "smtp.sendgrid.net", port 587 DEBUG SMTP RCVD: 220 SG ESMTP service ready at geopod-ismtpd-36 DEBUG: SMTPTransport connected to host "smtp.sendgrid.net", port: 587 DEBUG SMTP SENT: EHLO database_server DEBUG SMTP RCVD: 250-smtp.sendgrid.net 250-8BITMIME 250-PIPELINING 250-SIZE 31457280 250-SMTPUTF8 250-STARTTLS 250-AUTH PLAIN LOGIN 250 AUTH=PLAIN LOGIN DEBUG SMTP Found extension "8BITMIME", arg "" DEBUG SMTP Found extension "PIPELINING", arg "" DEBUG SMTP Found extension "SIZE", arg "31457280" DEBUG SMTP Found extension "SMTPUTF8", arg "" DEBUG SMTP Found extension "STARTTLS", arg "" DEBUG SMTP Found extension "AUTH", arg "PLAIN LOGIN" DEBUG SMTP Found extension "AUTH=PLAIN", arg "LOGIN" DEBUG SMTP: Attempt to authenticate DEBUG SMTP SENT: AUTH LOGIN DEBUG SMTP RCVD: 334 VXNlcm5hbWU6 DEBUG SMTP SENT: YXBpa2V5 DEBUG SMTP RCVD: 334 UGFzc3dvcmQ6 DEBUG SMTP SENT: encoded password DEBUG SMTP RCVD: 235 Authentication successful DEBUG SMTP: use8bit false DEBUG SMTP SENT: MAIL FROM:<myapp@example.net> DEBUG SMTP RCVD: 250 Sender address accepted DEBUG SMTP SENT: RCPT TO:<myrecipient@example.net> DEBUG SMTP RCVD: 250 Recipient address accepted Verified Addresses myrecipient@example.net DEBUG SMTP SENT: DATA DEBUG SMTP RCVD: 354 Continue DEBUG SMTP SENT: . *** 2025-06-10T07:58:54.209486-04:00 DEBUG SMTP RCVD: 250 Ok: queued as jYJnb995RBipUUlY_z2R5Q DEBUG SMTP SENT: QUIT
问题描述
代码在本地Java环境运行正常,但在Oracle数据库中通过存储过程调用时,调试日志中未出现STARTTLS命令,也未收到SendGrid返回的"220 Begin TLS negotiation now"响应,直接跳过TLS协商阶段进行了SMTP认证和邮件发送。
原因分析
- OJVM自带JavaMail版本冲突:Oracle 19c内置JVM(OJVM)默认包含旧版本JavaMail库,会优先加载自带版本而非部署的JavaMail 1.6.7,旧版本对
starttls.enable属性的处理逻辑不同,导致TLS协商未触发。 - 属性设置未生效:OJVM环境中,通过
Properties对象设置的JavaMail属性可能被系统级属性覆盖,或OJVM安全策略限制了属性修改。 - SSL信任存储不兼容:OJVM对自定义JKS信任存储支持有限,无法正确加载指定的
mytruststore.jks,导致JavaMail跳过TLS协商以规避SSL验证错误。
解决方法
1. 替换OJVM中的JavaMail版本
使用loadjava命令将JavaMail 1.6.7的mail.jar和activation.jar加载到Oracle数据库,覆盖自带旧版本:
loadjava -user <用户名>/<密码>@<数据库实例> -resolve -force -schema SECURITY mail.jar activation.jar
通过以下SQL验证类加载情况:
SELECT owner, object_name, object_type FROM all_objects WHERE object_name LIKE '%SMTPTransport%';
确保返回com.sun.mail.smtp.SMTPTransport且所属schema为部署的目标schema(如SECURITY)。
2. 修改属性设置方式
将STARTTLS相关属性通过System.setProperty全局设置,确保在OJVM中生效:
// 在创建props对象前添加 System.setProperty("mail.smtp.starttls.enable", "true"); System.setProperty("mail.smtp.starttls.required", "true"); System.setProperty("mail.smtp.ssl.protocols", "TLSv1.2");
3. 使用Oracle Wallet管理SSL证书
OJVM更适配Oracle Wallet,将SendGrid根证书导入Wallet并修改代码配置:
- 生成Oracle Wallet:
orapki wallet create -wallet /u02/sendgrid/wallet -pwd <wallet密码> -auto_login - 导入SendGrid根证书:
orapki wallet add -wallet /u02/sendgrid/wallet -trusted_cert -cert sendgrid_root.crt -pwd <wallet密码> - 修改代码中的SSL属性:
System.setProperty("javax.net.ssl.trustStoreType", "OraclePKI"); System.setProperty("javax.net.ssl.trustStore", "/u02/sendgrid/wallet/cwallet.sso"); - 配置数据库使用该Wallet:
重启数据库后生效。ALTER SYSTEM SET WALLET_LOCATION='(SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=/u02/sendgrid/wallet)))' SCOPE=SPFILE; ALTER SYSTEM SET SSL_CLIENT_AUTHENTICATION=FALSE SCOPE=SPFILE;
4. 验证TLS协商
修改后重新执行存储过程,正常调试日志会包含以下流程:
DEBUG SMTP SENT: EHLO database_server DEBUG SMTP RCVD: ...250-STARTTLS... DEBUG SMTP SENT: STARTTLS DEBUG SMTP RCVD: 220 Begin TLS negotiation now DEBUG: SMTPTransport converting to TLS mode
内容的提问来源于stack exchange,提问作者Nathan Russell
相关产品推荐
相关产品推荐

