Javamail在JDK8上无法SSL连接QQ邮箱服务器的解决方案
最近开发了一个工具,其中一个模块的功能是通过javamail发送邮件到指定地址,采用的smtp服务器是QQ邮箱(smtp.qq.com:465)。在本机自测功能的时候都是OK的,但在用户机器上执行发送邮件时却抛错了,查看了下异常信息,报的是javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure,这就意味着在建立ssl连接的时候加密套件协商失败。查看了下双方的JDK版本发现我的机器上运行的是JDK6,而用户运行的是JDK8,JDK8默认禁用了一些被认为不安全的加密套件,而QQ邮箱却强制使用了那些不安全的加密套件。JDK版本的不同导致的差异原因具体可以参见这里。
通过那篇博文提到的方法,我们可以发现要解决这个恶心的QQ邮箱RC4算法不兼容JDK8的问题只能替换JDK目录下的jce配置文件,但这种方法太过于本地化,无法将该解决方案应用到实际客户机上。因此再接着找方案,终于找到一个靠谱的,见这里,大致的意思就是通过反射获取那些定义安全限制的jce类,并做修改。我用这个方法测试后发现的确有效,但期间牵扯出另一个问题,就是从JDK8U102开始,javax.crypto.JceSecurity的isRestricted成员被修饰成了final属性,具体bug描述请见这里,这导致很多网站互相转载的所谓jce反射修改方案实际运行时会抛错:Can not set static final boolean field javax.crypto.JceSecurity.isRestricted to java.lang.Boolean,而我找到的那篇Stackoverflow上的回复却很好的解决了这个问题。
为了让代码看起来更容易理解,以及使用上的方便,我改进了去除JDK8安全限制的方法。JAVA类如下:
import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.security.Permission; import java.security.PermissionCollection; import java.util.Map; public class RemoveCryptographyRestrictions { private volatile static RemoveCryptographyRestrictions INSTANCE=null; public static void init() throws Exception { if(INSTANCE==null) synchronized (RemoveCryptographyRestrictions.class) { if(INSTANCE==null) INSTANCE=new RemoveCryptographyRestrictions(); } } private RemoveCryptographyRestrictions() throws Exception { Class<?> jceSecurity = getClazz("javax.crypto.JceSecurity"); Class<?> cryptoPermissions = getClazz("javax.crypto.CryptoPermissions"); Class<?> cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission"); if(jceSecurity==null||cryptoPermissions==null||cryptoAllPermission==null) return; setFinalStaticValue(jceSecurity, "isRestricted", false); PermissionCollection defaultPolicy = getFieldValue(jceSecurity, "defaultPolicy", null, PermissionCollection.class); Map<?, ?> map=getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class); map.clear(); Permission permission=getFieldValue(cryptoAllPermission, "INSTANCE", null, Permission.class); defaultPolicy.add(permission); } private Class<?> getClazz(String className) { Class<?> clazz=null; try { clazz=Class.forName(className); } catch (Exception e) { } return clazz; } private void setFinalStaticValue(Class<?> srcClazz, String fieldName, Object newValue) throws Exception { Field field=srcClazz.getDeclaredField(fieldName); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(null, newValue); } private <T> T getFieldValue(Class<?> srcClazz, String fieldName, Object owner, Class<T> dstClazz) throws Exception { Field field=srcClazz.getDeclaredField(fieldName); field.setAccessible(true); return dstClazz.cast(field.get(owner)); } }
使用方法非常简单,只要在你的程序的入口位置加入RemoveCryptographyRestrictions.init();就完成啦~
» 转载请注明来源:Terence的窝 » 《Javamail在JDK8上无法SSL连接QQ邮箱服务器的解决方案》
最新评论