首页 > 开发技术 > Javamail在JDK8上无法SSL连接QQ邮箱服务器的解决方案

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

2016年12月26日 Terence 226 次阅读 发表评论 阅读评论

      最近开发了一个工具,其中一个模块的功能是通过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();就完成啦~





分类: 开发技术 标签: ,
  1. 本文目前尚无任何评论.