【问题标题】:Error with Java WebStart Signed Jars on 1.7.0_401.7.0_40 上的 Java WebStart 签名 Jar 出错
【发布时间】:2014-03-27 13:36:07
【问题描述】:

我们知道 jar 软链接器的问题 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6967414 http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618

并使用了以下类(在网络上找到并修改以处理 JAVA 7。)


import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
* A utility class for working around the java webstart jar signing/security bug
* <p/>
* see http://bugs.sun.com/view_bug.do?bug_id=6967414 and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
*
* @author Scott Chan
*/
public class JarSignersHardLinker {


  private static final String JRE_1_DOT = "1.";
    private static final String DOT_ZERO_UNDERSCORE = ".0_";


  /**
  * the 1.6.0 update where this problem first occurred
  */
  private static final int PROBLEM_JRE_UPDATE = 19;


    private static final int PROBLEM_JRE_MAJOR_VERSION = 6;


  public static final List sm_hardRefs = new ArrayList();


  protected static void makeHardSignersRef(JarFile jar) throws java.io.IOException {
  if (jar != null && jar.getClass().getName().equals("com.sun.deploy.cache.CachedJarFile")) {
             Logger.info("Making hard refs for: " + jar.getName());
  //lets attempt to get at the each of the soft links.
  //first need to call the relevant no-arg method to ensure that the soft ref is populated
  //then we access the private member, resolve the softlink and throw it in a static list.
  callNoArgMethod("getSigners", jar);
  makeHardLink("signersRef", jar);
  callNoArgMethod("getSignerMap", jar);
  makeHardLink("signerMapRef", jar);


//            callNoArgMethod("getCodeSources", jar);
//            makeHardLink("codeSourcesRef", jar);
  callNoArgMethod("getCodeSourceCache", jar);
  makeHardLink("codeSourceCacheRef", jar);


  }
  }


  /**
  * if the specified field for the given instance is a Softreference
  * That soft reference is resolved and the returned ref is stored in a static list,
  * making it a hard link that should never be garbage collected
  *
  * @param fieldName
  * @param instance
  */
  private static void makeHardLink(String fieldName, Object instance) {
  //System.out.println("attempting hard ref to " + instance.getClass().getName() + "." + fieldName);
  try {
  Field signersRef = instance.getClass().getDeclaredField(fieldName);
  signersRef.setAccessible(true);
  Object o = signersRef.get(instance);
  if (o instanceof SoftReference) {
  SoftReference r = (SoftReference) o;
  Object o2 = r.get();
  sm_hardRefs.add(o2);
  } else {
  Logger.warn(fieldName + ": is not an instance of soft reference");
  }
  } catch (NoSuchFieldException e) {
  e.printStackTrace();
  return;
  } catch (IllegalAccessException e) {
  e.printStackTrace();
  }
  }


  /**
  * Call the given no-arg method on the given instance
  *
  * @param methodName
  * @param instance
  */
  private static void callNoArgMethod(String methodName, Object instance) {
  // System.out.println("calling noarg method hard ref to " + instance.getClass().getName() + "." + methodName + "()");
  try {
  Method m = instance.getClass().getDeclaredMethod(methodName);
  m.setAccessible(true);
  m.invoke(instance);


  } catch (SecurityException e1) {
  e1.printStackTrace();
  } catch (NoSuchMethodException e1) {
  e1.printStackTrace();
  } catch (IllegalArgumentException e) {
  e.printStackTrace();
  } catch (IllegalAccessException e) {
  e.printStackTrace();
  } catch (InvocationTargetException e) {
  e.printStackTrace();
  }
  }


  /**
  * is the preloader enabled. ie: will the preloader run in the current environment
  *
  * @return
  */
  public static boolean isHardLinkerEnabled() {
  boolean isHardLinkerDisabled = false;  //change this to use whatever mechanism you use to enable or disable the preloader
  return !isHardLinkerDisabled && isRunningOnJre1_6_0_19OrHigher() && isRunningOnWebstart();
  }


  /**
  * is the application currently running on webstart
  * <p/>
  * detect the presence of a JNLPclassloader
  *
  * @return
  */
  public static boolean isRunningOnWebstart() {
  ClassLoader cl = Thread.currentThread().getContextClassLoader();
  while (cl != null) {
  if (cl.getClass().getName().equals("com.sun.jnlp.JNLPClassLoader")) {
  return true;
  }
  cl = cl.getParent();
  }
  return false;


  }


  /**
  * Is the JRE 1.6.0_19 or higher?
  * TBFI-5349: Java has a bug, sometimes Jars get garbage collected. To resolve this we are making
     * hard references to the Jars.
     *
     * This method checks for java version. The bug is in 1.6.0_19 and above release hence checking for version 19
  * @return
  */
  public static boolean isRunningOnJre1_6_0_19OrHigher() {
        String javaVersion = System.getProperty("java.version");


        String updateStr = null;
        String javaMajorVersionStr = null;
        boolean isHardReferenceRequired = false;


        // Problem persist in JAVA 7 and probable in JAVA 8 as well. So changing the patter.
        Pattern pattern = Pattern.compile(JRE_1_DOT + "([6-9]+)" + DOT_ZERO_UNDERSCORE + "([0-9]+)(.*)");


        Matcher matcher = pattern.matcher(javaVersion);


        while (matcher.find()) {
            javaMajorVersionStr = matcher.group(1);
            updateStr = matcher.group(2);
            break;
        }
        Logger.info("Java version: " + javaMajorVersionStr + " update string: " + updateStr);
        try {
            if (javaMajorVersionStr != null) {


                int java_version = Integer.parseInt(javaMajorVersionStr);


                if (java_version > PROBLEM_JRE_MAJOR_VERSION) {
                    isHardReferenceRequired = true;
                } else if (java_version == PROBLEM_JRE_MAJOR_VERSION && Integer.parseInt(updateStr) >= PROBLEM_JRE_UPDATE) {
                    isHardReferenceRequired = true;
                }
            }
            return isHardReferenceRequired;


        } catch (NumberFormatException e) {
            //then unable to determine java Major version or update level
            e.printStackTrace();
            return isHardReferenceRequired;
        }
    }


  /**
  * get all the JarFile objects for all of the jars in the classpath
  *
  * @return
  */
  public static Set<JarFile> getAllJarsFilesInClassPath() {
  Set<JarFile> jars = new LinkedHashSet<JarFile>();
  for (URL url : getAllJarUrls()) {
  try {
  jars.add(getJarFile(url));
  } catch (IOException e) {
                Logger.error("unable to retrieve jar at URL: " + url);
  }
  }
  return jars;
  }


  /**
  * Returns set of URLS for the jars in the classpath.
  * URLS will have the protocol of jar eg: jar:http://HOST/PATH/JARNAME.jar!/META-INF/MANIFEST.MF
  */
  static Set<URL> getAllJarUrls() {
  try {
  Set<URL> urls = new LinkedHashSet<URL>();
  Enumeration<URL> mfUrls = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
  while (mfUrls.hasMoreElements()) {
  URL jarUrl = mfUrls.nextElement();
//                System.out.println(jarUrl);
  if (!jarUrl.getProtocol().equals("jar")) {
  continue;
  }
  urls.add(jarUrl);
  }
  return urls;
  } catch (IOException e) {
  throw new RuntimeException(e);
  }
  }


  /**
  * get the jarFile object for the given url
  *
  * @param jarUrl
  * @return
  * @throws IOException
  */
  public static JarFile getJarFile(URL jarUrl) throws IOException {
  URLConnection urlConnnection = jarUrl.openConnection();
  if (urlConnnection instanceof JarURLConnection) {
  // Using a JarURLConnection will load the JAR from the cache when using Webstart 1.6
  // In Webstart 1.5, the URL will point to the cached JAR on the local filesystem
  JarURLConnection jcon = (JarURLConnection) urlConnnection;
  return jcon.getJarFile();
  } else {
  throw new AssertionError("Expected JarURLConnection");
  }
  }


  /**
  * Spawn a new thread to run through each jar in the classpath and create a hardlink
  * to the jars softly referenced signers infomation.
  */
  public static void go() {
  if (!isHardLinkerEnabled()) {
  return;
  }
  Logger.info("Starting Resource Preloader Hardlinker");
  Thread t = new Thread(new Runnable() {


  public void run() {
  try {
  Set<JarFile> jars = getAllJarsFilesInClassPath();
  for (JarFile jar : jars) {
  makeHardSignersRef(jar);
  }
  } catch (Exception e) {
  Logger.warn("Problem preloading resources", e);
  } catch (Error e) {
                    Logger.error("Error preloading resources", e);
  }
  }
  });
  t.start();
  }
}

当我们使用 JRE6 启动应用程序时,它工作正常。但问题出在 JRE7 上。当应用程序使用 JRE7 启动时,我们在日志中得到以下异常。从异常中我们知道 jars 不是硬引用的,如果 jars 被垃圾收集,用户可能会遇到问题。我们下周有一个版本,需要找到解决此问题的方法。

java.lang.NoSuchMethodException: com.sun.deploy.cache.CachedJarFile.getSigners()
  at java.lang.Class.getDeclaredMethod(Unknown Source)
  at com.XXXXXX.ui.main.JarSignersHardLinker.callNoArgMethod(JarSignersHardLinker.java:96)
  at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardSignersRef(JarSignersHardLinker.java:45)
  at com.XXXXXX.ui.main.JarSignersHardLinker$1.run(JarSignersHardLinker.java:262)
  at java.lang.Thread.run(Unknown Source)
java.lang.NoSuchFieldException: signersRef
  at java.lang.Class.getDeclaredField(Unknown Source)
  at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardLink(JarSignersHardLinker.java:69)
  at com.XXXXXX.ui.main.JarSignersHardLinker.makeHardSignersRef(JarSignersHardLinker.java:46)
  at com.XXXXXX.ui.main.JarSignersHardLinker$1.run(JarSignersHardLinker.java:262)
  at java.lang.Thread.run(Unknown Source)

此异常重复 52 次(针对所有 JAR)。

我们已确保所有 52 个 jars/文件(JNLP 本身除外)都已正确签名,并且在启动应用程序之前清除了 java 缓存。

使用的JAVA版本是windows机器上的JDK 7u40。

尝试的选项是:

  1. 删除 jdk.certpath.disabledAlgorithms=MD2, RSA keySize
  2. 检查 jar 签名者证书。签名者证书使用 SHA1withRSA 作为签名算法。

注意:

  1. 源代码在 JAVA 5u11 中编译并在 JAVA 7u40 中运行
  2. 我们观察到了另一个差异。使用 JRE6,当我们运行相同的 一段代码,它首先从 java/jre6/lib 路径加载 JAVAWS.jar、Deploy.jar 和插件 .jar,但对于 JRE7,这些 jar 不会加载。

这已在 64 位和 32 位 java 版本中尝试过,但没有成功。

非常感谢这里的任何帮助。

【问题讨论】:

  • 这是您在部署 jar 之前对它们进行签名的方式吗?你试过&lt;jarsign/&gt;蚂蚁任务吗?这个在 jdk1.7 更新 45 之前没有给我任何问题

标签: java classloader java-web-start jnlp jarsigner


【解决方案1】:

@jorge_B:我们使用 ant 任务对 jar 进行签名。但是由于 JAVA 6u19 中的一个问题(软引用的 jar 有时会被垃圾收集),我们决定在 Java 版本 6u19 或更高版本时立即硬链接这些 jar。此问题与 jar 签名无关。问题是当我们尝试硬引用罐子时。我们的 jar 没有被硬引用,我们在日志中得到了 nosuchmethod 和 nosuchfield 异常。

【讨论】:

    【解决方案2】:
    import java.io.IOException;
    import java.lang.ref.SoftReference;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.JarURLConnection;
    import java.net.URL;
    import java.net.URLConnection;
    import java.util.*;
    import java.util.jar.JarFile;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * A utility class for working around the java webstart jar signing/security bug
     * <p/>
     * see http://bugs.sun.com/view_bug.do?bug_id=6967414 and http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6805618
     *
     * @author Scott Chan
     */
    public class JarSignersHardLinker {
    
        private static final String JRE_VERSION_START = "1.";
        private static final int PROBLEM_JRE_VERSION = 6;
        private static final String DOT_ZERO = ".0_";
    
        /**
         * the 1.6.0 update where this problem first occurred
         */
        private static final int PROBLEM_JRE_UPDATE = 19;
        private static String majorVersionStr = null;
        public static final List sm_hardRefs = new ArrayList();
    
        protected static void makeHardSignersRef(JarFile jar) throws java.io.IOException {
            Logger.info("Making hard refs for: " + (jar != null ? jar.getName() : null) + " with Java Version: "+majorVersionStr);
            if (jar != null && jar.getClass().getName().equals("com.sun.deploy.cache.CachedJarFile")) {
    
                //lets attempt to get at the each of the soft links.
                //first neet to call the relevant no-arg method to ensure that the soft ref is populated
                //then we access the private member, resolve the softlink and throw it in a static list.
                if (majorVersionStr != null && Integer.parseInt(majorVersionStr) > PROBLEM_JRE_VERSION) {
                    callNoArgMethod("getSigningData", jar);
                    makeHardLink("signingDataRef", jar);
                } else {
                    callNoArgMethod("getSigners", jar);
                    makeHardLink("signersRef", jar);
                    callNoArgMethod("getSignerMap", jar);
                    makeHardLink("signerMapRef", jar);
    
    //            callNoArgMethod("getCodeSources", jar);
    //            makeHardLink("codeSourcesRef", jar);
                    callNoArgMethod("getCodeSourceCache", jar);
                    makeHardLink("codeSourceCacheRef", jar);
                }
            }
        }
    
        /**
         * if the specified field for the given instance is a Softreference
         * That soft reference is resolved and the returned ref is stored in a static list,
         * making it a hard link that should never be garbage collected
         *
         * @param fieldName
         * @param instance
         */
        private static void makeHardLink(String fieldName, Object instance) {
            Logger.info("attempting hard ref to " + instance.getClass().getName() + "." + fieldName);
            try {
                Field signersRef = instance.getClass().getDeclaredField(fieldName);
                signersRef.setAccessible(true);
                Object o = signersRef.get(instance);
                if (o instanceof SoftReference) {
                    SoftReference r = (SoftReference) o;
                    Object o2 = r.get();
                    sm_hardRefs.add(o2);
                } else {
                    Logger.warn(fieldName + ": is not an instance of soft reference");
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                return;
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Call the given no-arg method on the given instance
         *
         * @param methodName
         * @param instance
         */
        private static void callNoArgMethod(String methodName, Object instance) {
            Logger.info("calling noarg method hard ref to " + instance.getClass().getName() + "." + methodName + "()");
            try {
                Method m = instance.getClass().getDeclaredMethod(methodName);
                m.setAccessible(true);
                m.invoke(instance);
    
            } catch (SecurityException e1) {
                e1.printStackTrace();
            } catch (NoSuchMethodException e1) {
                e1.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * is the preloader enabled. ie: will the preloader run in the current environment
         *
         * @return
         */
        public static boolean isHardLinkerEnabled() {
            boolean isHardLinkerDisabled = false;  //change this to use whatever mechanism you use to enable or disable the preloader
            return !isHardLinkerDisabled && isRunningOnJre1_6_0_19OrHigher() && isRunningOnWebstart();
        }
    
        /**
         * is the application currently running on webstart
         * <p/>
         * detect the presence of a JNLPclassloader
         *
         * @return
         */
        public static boolean isRunningOnWebstart() {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            while (cl != null) {
                if (cl.getClass().getName().equals("com.sun.jnlp.JNLPClassLoader")) {
                    return true;
                }
                cl = cl.getParent();
            }
            return false;
    
        }
    
        /**
         * Is the JRE 1.6.0_19 or higher?
         *
         * @return
         */
    
        public static boolean isRunningOnJre1_6_0_19OrHigher() {
            String javaVersion = System.getProperty("java.version");
            // Sometimes java releases version 1.X.0_YY-rev for specific issue and specific user,
            // to resolve this we use patter instead of string split.
            //ERP-6460: Checking whether JRE is 6 update 19 or higher
            String updateStr = null;
            majorVersionStr = null;
            boolean isHardReferenceRequired = false;
    
            Pattern pattern = Pattern.compile(JRE_VERSION_START + "([6-9]+)" + DOT_ZERO + "([0-9]+)(.*)");
            Matcher matcher = pattern.matcher(javaVersion);
    
            while (matcher.find()) {
                majorVersionStr = matcher.group(1);
                updateStr = matcher.group(2);
                break;
            }
    
            try {
                if (majorVersionStr != null) {
                    int java_version = Integer.parseInt(majorVersionStr);
                    if (java_version > PROBLEM_JRE_VERSION) {
                        isHardReferenceRequired = true;
                    } else if (java_version == PROBLEM_JRE_VERSION && Integer.parseInt(updateStr) >= PROBLEM_JRE_UPDATE) {
                        isHardReferenceRequired = true;
                    }
                }
                return isHardReferenceRequired;
            } catch (NumberFormatException e) {
                e.printStackTrace();
                return isHardReferenceRequired;
            }
        }
    
        /**
         * get all the JarFile objects for all of the jars in the classpath
         *
         * @return
         */
        public static Set<JarFile> getAllJarsFilesInClassPath() {
            Set<JarFile> jars = new LinkedHashSet<JarFile>();
            for (URL url : getAllJarUrls()) {
                try {
                    jars.add(getJarFile(url));
                } catch (IOException e) {
                    Logger.error("unable to retrieve jar at URL: " + url);
                }
            }
            return jars;
        }
    
        /**
         * Returns set of URLS for the jars in the classpath.
         * URLS will have the protocol of jar eg: jar:http://HOST/PATH/JARNAME.jar!/META-INF/MANIFEST.MF
         */
        static Set<URL> getAllJarUrls() {
            try {
                Set<URL> urls = new LinkedHashSet<URL>();
                Enumeration<URL> mfUrls = Thread.currentThread().getContextClassLoader().getResources("META-INF/MANIFEST.MF");
                while (mfUrls.hasMoreElements()) {
                    URL jarUrl = mfUrls.nextElement();
    //                System.out.println(jarUrl);
                    if (!jarUrl.getProtocol().equals("jar")) {
                        continue;
                    }
                    urls.add(jarUrl);
                }
                return urls;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * get the jarFile object for the given url
         *
         * @param jarUrl
         * @return
         * @throws IOException
         */
        public static JarFile getJarFile(URL jarUrl) throws IOException {
            URLConnection urlConnnection = jarUrl.openConnection();
            if (urlConnnection instanceof JarURLConnection) {
                // Using a JarURLConnection will load the JAR from the cache when using Webstart 1.6
                // In Webstart 1.5, the URL will point to the cached JAR on the local filesystem
                JarURLConnection jcon = (JarURLConnection) urlConnnection;
                return jcon.getJarFile();
            } else {
                throw new AssertionError("Expected JarURLConnection");
            }
        }
    
        /**
         * Spawn a new thread to run through each jar in the classpath and create a hardlink
         * to the jars softly referenced signers infomation.
         */
        public static void go() {
            if (!isHardLinkerEnabled()) {
                return;
            }
            Logger.info("Starting Resource Preloader Hardlinker");
            Thread t = new Thread(new Runnable() {
    
                public void run() {
                    try {
                        Set<JarFile> jars = getAllJarsFilesInClassPath();
                        for (JarFile jar : jars) {
                            makeHardSignersRef(jar);
                        }
    
                    } catch (Exception e) {
                        Logger.error("Problem preloading resources", e);
                    } catch (Error e) {
                        Logger.error("Error preloading resources", e);
                    }
                }
    
            });
            t.start();
    
        }
    }
    

    【讨论】:

    • 使用以上代码解决问题。问题的原因是方法的变化。对于 JAVA 7u40 及更高版本,您可以使用以下方法 callNoArgMethod("getSigningData", jar) 和 makeHardLink("signingDataRef", jar);
    • 上面的代码解决了我们的问题,并且在上面的代码之后一切都开始正常工作了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-29
    • 2013-09-02
    • 2011-06-25
    • 2013-03-15
    相关资源
    最近更新 更多