1.7.0_40上的Java WebStart Signed Jars出错

时间:2014-02-25 11:07:55

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

我们知道jar softlinker的问题 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启动应用程序时,我们在日志中得到以下异常。从例外情况我们知道罐子没有硬引用,如果罐子被垃圾收集,用户可能会遇到问题。我们下周将发布一个版本,需要找到解决此问题的方法。

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个jar /文件(JNLP本身除外)都已正确签名,并且在启动应用程序之前java缓存已清除。

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

尝试的选项是:

  1. 删除jdk.certpath.disabledAlgorithms = MD2,RSA keySize&lt; 1024 来自java.securites文件。
  2. 检查jar签名者证书。签名者证书使用SHA1withRSA作为签名算法。
  3. 注意:

    1. 源代码在JAVA 5u11中编译并在JAVA 7u40中运行
    2. 我们发现了另外一个区别。使用JRE6,当我们运行相同的时候 一段代码,它首先从java / jre6 / lib路径中获取JAVAWS.jar,Deploy.jar和插件.jar,但是使用JRE7,这些jar都没有加载。
    3. 这已经在64位和32位java版本中尝试过但没有运气。

      非常感谢任何帮助。

2 个答案:

答案 0 :(得分:0)

@jorge_B:我们使用ant任务签署罐子。但是由于JAVA 6u19中的一个问题(软件引用的罐子有时是垃圾收集的)我们决定在我们点击java版本6u19或更高版本时立即硬连接罐子。这个问题不适用于jar签名。问题是当我们试图硬引用罐子时。我们的罐子没有被硬引用,我们在日志中得到了nosuchmethod和nosuchfield异常。

答案 1 :(得分:0)

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();

    }
}