手动加载本机库以规避限制性环境

时间:2015-07-20 21:10:18

标签: java jdbc dll java-native-interface classloader

我正在维护一个需要连接到Microsoft SQL Server实例的Java Swing应用程序。出于各种原因,我选择替换与jTDS一起使用的本机SQL Server驱动程序(前面提到的Microsoft驱动程序当时没有工作,并且在该领域中显然也失败了)。当我尝试在IDE之外运行可执行文件.jar时,我遇到了问题,因为我错过了相应的ntlmauth.dll依赖项。

在继续之前,请务必注意,此应用程序正在极其严格限制(仅限Windows)的环境中开发和使用:

  • 我无法安装任何需要Windows UAC身份验证的软件
  • 我的用户无法安装或运行任何需要UAC身份验证的软件
  • 这意味着我无法将文件写入System32或JAVA_HOME,也无法使用任何类型的ProcessBuilder tomfoolery使用我需要的任何命令行参数启动另一个JVM
  • 我不能使用只能在首次安装/设置时需要UAC权限的可执行包装/安装程序

我正在尝试的解决方案是this onethis one to check it的组合 - 基本上将.dll打包在.jar中,然后将其解压缩并在必要时加载它 - 就像大多数我发现的其他解决方案与上述限制不相符;但是,我遇到了一个问题,即使在本机库表面上已经“加载”之后,我也会遇到一个例外,说它不是。

我的启动前代码:

private static final String LIB_BIN = "/lib-bin/";
private static final String JTDS_AUTH = "ntlmauth";

// load required JTDS binaries
static {
    logger.info("Attempting to load library {}.dll", JTDS_AUTH);
    try {
        System.loadLibrary(JTDS_AUTH);
    } catch (UnsatisfiedLinkError e) {
        loadFromJar();
    }

    try {
        // do some quick checks to make sure that went ok
        NativeLibraries nl = new NativeLibraries();
        logger.debug("Loaded libraries: {}", nl.getLoadedLibraries().toString());
    } catch (NoSuchFieldException ex) {
        logger.info("Native library checker load failed", ex);
    }
}

/**
 * When packaged into JAR extracts DLLs, places these into
 */
private static void loadFromJar() {
    // we need to put DLL in temp dir
    String path = ***;
    loadLib(path, JTDS_AUTH);
}

/**
 * Puts library to temp dir and loads to memory
 */
private static void loadLib(String path, String name) {
    name = name + ".dll";
    try {
        // have to use a stream
        InputStream in = net.sourceforge.jtds.jdbc.JtdsConnection.class.getResourceAsStream(LIB_BIN + name);
        // always write to different location
        File fileOut = new File(System.getProperty("java.io.tmpdir") + "/" + path + LIB_BIN + name);
        logger.info("Writing dll to: " + fileOut.getAbsolutePath());
        OutputStream out = FileUtils.openOutputStream(fileOut);
        IOUtils.copy(in, out);
        in.close();
        out.close();
        System.load(fileOut.toString());
    } catch (Exception e) {
        logger.error("Exception with native library loader", e);
        JOptionPane.showMessageDialog(null, "Exception loading native libraries: " + e.getLocalizedMessage(), "Exception", JOptionPane.ERROR_MESSAGE);
    }
}

正如您所看到的,我基本上从第一个链接逐字复制了解决方案,只需稍作修改即可尝试运行应用程序。我还从第二个链接复制了该类并将其命名为NativeLibraries,该方法的调用相当无关,但它显示在日志中。

无论如何,这里是启动应用程序时日志输出的相关位:

    2015-07-20 12:32:33 INFO  - Attempting to load library ntlmauth.dll
    2015-07-20 12:32:33 INFO  - Writing dll to: C:\Users\***\lib-bin\ntlmauth.dll
    2015-07-20 12:32:33 DEBUG - Loaded libraries: [C:\Program Files\Java\jre1.8.0_45\bin\zip.dll, C:\Program Files\Java\jre1.8.0_45\bin\prism_d3d.dll, C:\Program Files\Java\jre1.8.0_45\bin\prism_sw.dll, C:\Program Files\Java\jre1.8.0_45\bin\msvcr100.dll, C:\Program Files\Java\jre1.8.0_45\bin\glass.dll, C:\Program Files\Java\jre1.8.0_45\bin\net.dll, C:\Users\***\lib-bin\ntlmauth.dll]
    2015-07-20 12:32:33 INFO  - Application startup
    ***
    2015-07-20 12:32:36 ERROR - Database exception
    java.sql.SQLException: I/O Error: SSO Failed: Native SSPI library not loaded. Check the java.library.path system property.
at net.sourceforge.jtds.jdbc.TdsCore.login(TdsCore.java:654) ~[jtds-1.3.1.jar:1.3.1]
at net.sourceforge.jtds.jdbc.JtdsConnection.<init>(JtdsConnection.java:371) ~[jtds-1.3.1.jar:1.3.1]
at net.sourceforge.jtds.jdbc.Driver.connect(Driver.java:184) ~[jtds-1.3.1.jar:1.3.1]
at java.sql.DriverManager.getConnection(Unknown Source) ~[na:1.8.0_45]
at java.sql.DriverManager.getConnection(Unknown Source) ~[na:1.8.0_45]    

可以看到该库实际上已从日志中的第三行“加载”(如果您不想滚动,则它是最后一个条目)。但是,我只是使用了我觉得可能正在使用本机库的类(我也试过TdsCore类无济于事),因为显示如何执行此操作的示例只是使用了库中包含的随机类。需要。

这里有什么我想念的吗?我对JNI或ClassLoader的内部工作方式不是很熟悉,所以我可能只是错误地加载它。任何建议或建议将不胜感激!

1 个答案:

答案 0 :(得分:1)

Welp我想出了一个解决方法:我最终使用了JarClassLoader。这基本上需要将我的所有依赖项(包括Java和本机)复制到&#34;库&#34;我的主.jar中的文件夹,以及在IDE中禁用.jar签名。然后,应用程序由一个新类运行,该类只是创建一个新的JarClassLoader对象并运行&#34; invokeMain&#34;方法 - 一个例子在网站上。经过几天将我的头撞在墙上,整件事花了大约三分钟。

希望有一天能帮到某人!