Problem
在整合SMTP功能時,如果要提供SSL或StartTLS的方式與SMTP server溝通,一定會遇到certificate hostname驗證的問題;因為內部的mail server不一定會提供合法的certificate。例外可能會像這樣:
Caused by: java.io.IOException: Server is not trusted: tonylin.idv.tw at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:564) at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:486) at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:1902) ... 30 more
本篇文章,主要分享透過程式將目標機器設定為可信任的方式。
How to?
第一個方法是設定property(必須沒設定SocketFactory,而javamail預設會使用MailSSLSocketFactory):
prop.put("mail.smtp.ssl.trust", "tonylin.idv.tw");
有多個可以使用空白分格,如果要允許全部就使用*。
第二個方法是直接使用MailSSLSocketFactory,針對SSL的情況要使用mail.smtp.socketFactory:
MailSSLSocketFactory socketFactory = new MailSSLSocketFactory(); socketFactory.setTrustedHosts(new String[]{"tonylin.idv.tw"}); socketFactory.setTrustAllHosts(true); prop.put("mail.smtp.socketFactory", socketFactory);
而StartTLS會經過將普通連線變為TLS連線的階段,所以要使用mail.smtp.ssl.socketFactory:
prop.put("mail.smtp.starttls.enable","true"); prop.put("mail.smtp.ssl.socketFactory", socketFactory);
為了可以針對是否驗證做切換,我是這樣處理:
private SocketFactory getMailSSLSocketFactory() { try { if (ignoreCertValidation) { MailSSLSocketFactory factory = new MailSSLSocketFactory(); factory.setTrustAllHosts(true); return factory; } return SSLSocketFactory.getDefault(); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } switch (getEncryption()) { case SSL: prop.put("mail.smtp.socketFactory", getMailSSLSocketFactory()); break; case TLS: prop.put("mail.smtp.ssl.socketFactory", getMailSSLSocketFactory()); prop.put("mail.smtp.starttls.enable", "true"); break; default: break; }
除了以上方法,你也可以使用自己SocketFactory去解決這問題。我們也有程式可以在runtime將目標機器的certificate加到keystore中。
留言
張貼留言