IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Javamail在JDK8上无法SSL连接QQ邮箱服务器的解决方案

    Terence发表于 2016-12-26 10:50:00
    love 0

          最近开发了一个工具,其中一个模块的功能是通过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邮箱服务器的解决方案》

    相关日志

    • Spring容器在Oracle JDK和OpenJDK中的类装载差异导致的自动装箱bug问题 (1)
    • Hibernate中变通使用Criteria API对自定义SQL表达式列进行排序操作 (2)
    • Eclipse性能优化实践 (0)


沪ICP备19023445号-2号
友情链接