Java: java.util.Preferences 失败

66

我的程序使用java.util.Preferences类(系统首选项,而不是用户)将加密的产品密钥数据保存到计算机上。问题是,在Windows和Linux上(尚未在OSX上进行测试,但可能相同),如果我不以sudo或管理员权限运行程序,则每当尝试读取或保存数据时都会发出异常或警告。

显然,要求用户以管理员权限运行程序是不切实际的。最理想的情况是,我希望操作系统询问用户是否允许。

这很愚蠢,并且去掉了Preferences的一半用途。如何解决这个问题?

这是我需要的简要概述:我需要我的程序请求操作系统允许保存系统设置。


以下是错误信息

当我尝试读取一个节点(因为该节点不存在)时,出现以下错误:

Mar 18, 2011 9:41:15 AM java.util.prefs.WindowsPreferences <init>
WARNING: Could not create windows registry node Software\JavaSoft\Prefs\myapp at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
Mar 18, 2011 9:41:15 AM java.util.prefs.WindowsPreferences WindowsRegOpenKey1
WARNING: Trying to recreate Windows registry node Software\JavaSoft\Prefs\myapp at root 0x80000002.
Mar 18, 2011 9:41:15 AM java.util.prefs.WindowsPreferences openKey
WARNING: Could not open windows registry node Software\JavaSoft\Prefs\myapp at root 0x80000002. Windows RegOpenKey(...) returned error code 2.
Mar 18, 2011 9:41:15 AM java.util.prefs.WindowsPreferences WindowsRegOpenKey1
WARNING: Trying to recreate Windows registry node Software\JavaSoft\Prefs\myapp\subpackage at root 0x80000002.
Mar 18, 2011 9:41:15 AM java.util.prefs.WindowsPreferences openKey
WARNING: Could not open windows registry node Software\JavaSoft\Prefs\myapp\subpackage at root 0x80000002. Windows RegOpenKey(...) returned error code 2.

当我尝试写入节点时,发生了以下情况:

Mar 18, 2011 9:43:11 AM java.util.prefs.WindowsPreferences WindowsRegOpenKey1
WARNING: Trying to recreate Windows registry node Software\JavaSoft\Prefs\myapp\subpackage at root 0x80000002.
Mar 18, 2011 9:43:11 AM java.util.prefs.WindowsPreferences openKey
WARNING: Could not open windows registry node Software\JavaSoft\Prefs\myapp\subpackage at root 0x80000002. Windows RegOpenKey(...) returned error code 2.

2
根据JavaDocs中的说明,Preferences类中的方法只有在存在安全管理器时才会抛出SecurityException异常。那么在您的代码中会抛出什么类型的异常呢? - Matt Ball
2
请尝试使用 userRoot() 而不是 systemRoot()。 - bestsss
2
@MattBall JavaDoc确实是这样说的,但实际上,当操作系统拒绝JVM写入首选项存储时,它也可能会抛出SecurityException(我在WindowsPreferences中看到了这一点,但由于任何首选项存储都可能这样做,因此需要注意。) - Hakanai
类似问题:https://dev59.com/nmQn5IYBdhLWcg3w9bGi - koppor
这是由一个错误引起的:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6790382 - Kristof Neirynck
显示剩余11条评论
11个回答

49
很遗憾,这里得到的大多数答案都是错误的......至少有点。因为症状被治疗了,而不是原因。
让我们回顾一下。Java Preferences有两个“树”:user treesystem tree。您可以编写自己的Java Preferences后端(称为备份存储),但很少有开发人员这样做,所以您最终会得到JDK的默认备份存储。在Windows平台上,这意味着Win Registry,更具体地说是:
- 用户树被写入HKEY_CURRENT_USER\Software\JavaSoft\Prefs(操作系统用户在此始终具有写访问权限) - 系统树被写入HKEY_LOCAL_MACHINE\Software\JavaSoft\Prefs(只有具有管理员特权的操作系统用户才具有写访问权限)
总之,只要你的代码不试图使用系统树,你就应该没问题,不需要在操作系统级别上更改权限。系统树是为“主机上的所有用户”而设计的,而用户树是为特定登录用户而设计的。在你的情况下,我相信你可以用用户树来满足需求,这真的是你的解决方案。不要去乱搞权限、以管理员身份运行等等。
......但还有更多。假设你的代码故意不触及Java首选项系统树,按照指示。然后在Windows上,你仍然会看到这个警告:
WARNING [java.util.prefs]: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.

所以发生了什么?我给了你错误的建议吗?并不是。跟着我走。
深入研究JDK源代码,你会看到0x80000002意味着HKLM,即Win注册表中不应该被触及的地方。你的代码从未涉及系统树,但你仍然看到这个警告!?(此时你一定像我一样抓狂了...)
好吧,这是极为罕见的情况之一,确实存在JDK bug。如果你对为什么微妙的漏洞在JDK中多年未被发现感兴趣,我鼓励你阅读this answer。这个bug从JDK 1.4开始就存在,但最近才得到修复,并且还没有被回溯到JDK 8(更新:现在已经将修复程序回溯到JDK 8u192及以上版本)。
最好的建议:
  • Make sure your code only references the user tree, not the system tree. It is only fair that the OS requires all kinds of privs for you to write to a system-wide location. If you really need to write into such a location then there really is no other solution than assigning privs, executing as Administrator or what not.

  • Ignore the warning. It'll go away once you are on Java 8 update 192 or later. The warning can be safely ignored.

  • Alternatively you can try to programmatically silence the warning. It comes from the JDK's Platform Logger, so something like this should work, albeit I haven't tried it myself:

      sun.util.logging.PlatformLogger platformLogger = PlatformLogger.getLogger("java.util.prefs");
      platformLogger.setLevel(PlatformLogger.Level.OFF);
    

你为我提供的最后一个修复方案出现了错误:Access restriction: The type 'PlatformLogger' is not API (restriction on required library 'C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar'),因此无法解析 PlatformLogger - xeruf
@Xerus。是的,在后来我了解到,你将无法接触到JDK的PlatformLogger。与其尝试以编程方式实现它,不如从您的日志配置文件中尝试,例如在其中放置像java.util.prefs.level = OFF这样的内容可能会奏效。 - peterh
我刚在Windows 10上安装了64位的JDK 9,但必要的注册表键仍未创建。奇怪的是,即使安装JDK 6也无法解决问题 - 我不得不手动创建注册表键“HKEY_LOCAL_MACHINE \ Software \ JavaSoft \ Prefs”和“HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ JavaSoft \ Prefs”...但这个问题最近才被修复,尚未向JDK 8回溯。 - Balder
1
@Balder。所谓“修复”,是指JDK源代码中的错误。您所说的是JRE/JDK 安装程序,但它不再创建这些注册表键...而且真的不应该这样做。请记住,Oracle的JRE/JDK旨在能够在未安装的情况下运行。这很好。您基本上只需下载一个zip文件,解压缩它,它就可以工作了。 - peterh

42

这个链接对我起作用:

解决问题 解决方法是以管理员身份登录并创建键HKEY_LOCAL_MACHINE\Software\JavaSoft\Prefs


1
那对我没起作用,我尝试这样做后,访问 Preferences.systemRoot() 时出现了异常 :( - vikingsteve
1
我发现我必须删除 Prefs 节点并重新创建它。 - Mark
10
虽然这可能有效,但似乎并不是一个解决方案。我不会发布需要终端用户编辑他们的注册表的应用程序。 - Menefee
@Menefee。你说得对。这只是在治疗症状,而不是根本原因。请参见我的回答 - peterh
1
在64位系统上,您还必须创建键“HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\JavaSoft\Prefs”。 - Balder

11

可以更改注册表项的访问权限。如果您允许所有人对HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs具有完全的访问权限,那么每个人都将看到相同的首选项集,并且每个人都可以全局更改它们。我知道这不是为客户安装的软件提供的解决方案,但对某些人可能会有用。


嗯,我在想安装程序在具备管理员权限时是否可以更改访问权限? - Jonah
好主意 - 这可以通过编程实现吗?理想情况下,最好能像这样安装程序 - 而无需管理员运行regedit。 - vikingsteve
2
这就是我打算做的事情——在我的安装程序中添加一个脚本,调用“regini”Windows命令来设置特定键的权限。也许不是在“HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs”上设置权限,而是在我需要访问的Prefs中的特定节点上设置权限。 - Shitesh

5

根据反馈修改答案。 这个解决方案可能过于复杂,但是...

  • 建议将您的存储更改为写入文件而不是注册表(示例)
  • 很多基于Java的产品都会随附自己的JVM。他们这样做是为了能够运行自定义策略文件(在您的情况下需要写入一个共同的位置)并节省支持问题(如使用过时/未经测试的JVM)。

但是我应该把文件保存在哪里呢?这是一个跨平台应用程序。程序不能直接请求权限吗?显然不能将其保存在用户的主目录中,因为那样其他账户将无法访问它。 - Jonah
您不需要管理员权限。您可能需要(管理员/高级用户)才能写入HKEY_LOCAL_MACHINE,但对于HKEY_CURRENT_USER,您可以使用任何用户(在vista/windows7 32位处理器上自动重定向到HKEY_CURRENT_USER)。 - bestsss

3
特别是在Windows 7上,JVM默认没有权限写入Windows注册表,而java.util.prefs.preferences的后备存储位置就在MS-Windows下的注册表中。
当执行ReverseXSL转换器或甚至是Regex测试程序时,可能会出现错误,如:无法打开/创建根节点Software\JavaSoft\Prefs,根为0x80000002。Windows RegCreateKeyEx。
这会阻止注册许可证。但不会阻止软件以免费软件模式执行转换。
解决问题只需授予相关注册表根键所需的权限。
以管理员身份运行regedit.exe(regedit.exe位于c:\Windows操作系统根目录中)。 转到HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs键。右键单击以设置权限。为需要执行reverseXSL软件的用户选中完全控制复选框。

2

只需以管理员身份运行应用程序,或者如果使用eclipse,则以管理员身份运行eclipse。


1
这可能可行,但将软件以管理员身份运行是一个非常糟糕的想法。 - ATorras
1
您只需要执行一次即可。因此,Java/Prefs 可以创建系统节点,然后如果您以普通用户身份运行它,就可以正常运行了。 - rednoah
在管理员模式下运行像Eclipse这样的大型终端用户应用程序是一个可怕的想法。任何Eclipse插件中的错误可能会在系统注册表、/etc或任何其他非常敏感的区域中肆虐;此外,您将永远无法在用户模式下使用Eclipse,因为Eclipse将开始编写只有管理员访问权限的文件。此外,Eclipse代码通常没有在管理员模式下进行良好行为测试。 - toolforger

2

1

peterh的答案已经详细解释了背景,但我正在寻找一个解决方法并找到了!

由于您无法触摸PlatformLogger本身,因此必须使其消息无效:

// get rid of the bugged Preferences warning
PrintStream err = System.err;
System.setErr(new PrintStream(new OutputStream() {
    public void write(int b) {}
}));
Preferences PREFS = Preferences.userNodeForPackage(Settings.class);
System.setErr(err);

这样可以消除令人烦恼的警告,而不留下任何痕迹。请注意,您只需要在程序中第一次引用首选项API的位置执行此操作。

似乎全局隐藏了警告。这样做后,我不再收到其他错误报告了。 - Eugene Kartoyev
1
你是正确的,有一个错误。现在已经被修复了 :) - xeruf
太棒了!现在我爱你,@Xerus! - Eugene Kartoyev

0
解决方法是以管理员身份运行JMeter,它会为您创建注册表键,然后您可以以普通用户身份重新启动JMeter,就不会再出现警告了。JMeter官方网站-更改

只是治疗症状,而不是原因。请参见我的答案,了解为什么这是一个坏主意。 - peterh

0
对我来说,解决方案并不明显——它是从Oracle更新我的加密安全JAR文件,因为似乎存在密钥长度限制(直到我尝试过后才相信它与此有关)。 从Oracle网站下载 下载包含说明并解释:

由于某些国家的进口控制限制,Java Runtime Environment或JRE(TM) 8环境中捆绑的JCE策略文件版本允许使用“强大”但受限的加密。此下载包(包括此README文件的那个)提供了“无限制强度”策略文件,其中不包含任何密码强度限制。

这显然也适用于注册表键。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接