如何在Tomcat环境中保存名称 – 值对?

我们有一个servlet需要某些变量,如密码,加密盐等,不能永久保存在文件系统中。 这就是我们目前所做的事情(摘要):

在初始化期间

  1. Perl脚本将ReadMode设置为2以屏蔽stdout echo,提示用户输入变量,过滤已知文件以将其放入并调用tomcat / bin / startup.sh

  2. servlet init()方法从文件中读取变量并删除它(文件)。

问题:当重新编译WAR时,tomcat尝试部署它(autodeploy = true),这是我们想要的。 但是数据文件不再存在,因此抛出了FileNotFoundException(正确地说是这样)。

问题:servlet可以使用属性或某些HashMap / Table,在手动启动期间可以存储一些变量吗? 我们的想法是,如果在重新部署期间数据文件不存在,init()可以检查它们。 谢谢, – MS。

Tomcat中没有任何东西可以做你想要的。 如果将设置移动到tomcat JNDI树中,则必须将用户/名称密码组合放在server.xml或context.xml文件中。 以下是针对您遇到的问题的几种可能的解决方案。

选项1:使用Tomcat侦听器

如果查看tomcat server.xml文件的顶部,您将看到几个侦听器,这些是在tomcat启动时执行的java类。 您可以创建自己的tomcat侦听器,该侦听器从文件系统读取密码,删除文件并以应用程序可访问的方式存储用户名/密码Comobo。 tomcat监听器绑定到tomcat服务器的生命周期,因此应用程序的自动重新部署不会导致重新加载tomcat监听器类。 tomcat侦听器的代码必须放在jar中并放在CATALINA_HOME \ lib文件夹中。

监听器可以使用静态变量来存储用户名/密码,并且有一个返回它们的静态方法,这将起作用,因为监听器将位于tomcat应用程序的父类加载器中。 这种方法的缺点是您的应用程序代码依赖于tomcat资源侦听器实现,并且在您的unit testing中,您可能需要执行一些额外的步骤以确保您的代码可以进行unit testing。

监听器还可以访问tomcat全局JNDI树并将用户名和密码组合放在那里,然后你的应用必须让context.xml文件使用ResourceLink元素来使应用程序可以使用全局jndi条目。 您还需要做一些额外的工作来使这种方法与unit testing一起工作,因为在JNDI中查找内容通常会使unit testing变得复杂。

如果您在此项目中使用Spring,最好的办法是在tomcat侦听器中使用静态变量,然后使用自定义弹簧范围将数据从tomcat侦听器中提取出来。 这样你的应用程序可以测试,你可以将用户名/密码组合注入任何需要它们的代码片段。

选项2:使用Servlet上下文侦听器

在此选项中,您可以编写一个上下文侦听器,它将允许您的应用在应用启动和停止时得到通知。 使用此方法启动时,上下文侦听器将运行并读取密码信息并删除该文件。 如果启动时没有密码文件,那么上下文监听器必须有办法让管理员重新生成文件。

选项3:使用JMX

创建一个JMX MBean,将其注册到JVM MBeanServer,然后使用它来存储用户名/密码组合。 如果从tomcat侦听器初始化此MBean,则可以让perl脚本远程调用MBean并传入用户名/密码组合。

希望这可以帮助。

当你调用startup.sh时,如何将它们作为系统属性传递,即。 ‘bin / startup.sh -DencryptionSalt = foobar’? 然后,您可以在JVM的持续时间内调用System.getProperty(“encryptionSalt”)。

将易失性数据放入JNDI。 重新部署之间不会清除JNDI。 您的servlet在init期间仍然可以执行相同的操作,以确保JNDI中的数据是最新的。

我不记得JNDI读/写是否是线程安全的,但事实并非如此,你总是可以放一个线程安全的对象(例如ConcurrentHashMap )并毫无问题地使用它。

编辑由MS:

 ========== restart.pl: ========== ReadMode 2; print "\nEnter spice: "; chomp ($spice = ); print "\n"; ReadMode 0; open (FP, ">$spicefile") or die "$0: Cannot write file $spicefile: $!\n"; print FP "$spice\n"; close (FP); system "bin/shutdown.sh"; sleep 8; system "bin/startup.sh"; sleep 8; system "wget $tomcaturl"; system '/bin/rm -rf *spicefile*'; # delete wget output file foreach $sCount (1..10) { # give it 10 more secs sleep 1; if (-f $spicefile) { print "$0: Waiting on servlet to delete spicefile [$sCount]\n" } else { print "$0: Successful servlet initialization, no spicefile\n"; exit 0 }} print "\n$0: deleting file $spicefile ...\n"; # Error condition system "unlink $spicefile"; exit 1; =========== context.xml ===========  =================== app/WEB-INF/web.xml ===================  packageName.DBInterface   Use spice as transferable during redeployment MyServlet/upsBean packageName.UPSBean  ============== MyServlet.java: ============== init() { if (new File (spiceFile).exists()) # Same name in restart.pl dbi = new DBInterface (spiceFile); else dbi = new DBInterface (); # Redeployment otherInitializations (dbi.getSpice()); } ================ DBInterface.java: ================ public DBInterface () { // Comment out following block if contextInitialized works FileInputStream fin = new FileInputStream (safeDepositBox); ObjectInputStream ois = new ObjectInputStream (fin); UPSBean upsBean = (UPSBean) ois.readObject(); ois.close(); spice = upsBean.getSpice(); dbiIndex = 2; // do stuff with spice } public DBInterface (String spiceFileName) { File file = new File (spiceFileName); BufferedReader br = new BufferedReader (new FileReader (file)); spice = br.readLine(); br.close(); file.delete(); dbiIndex = 1; // Delete following block if contextInitialized works UPSBean upsBean = new UPSBean(); upsBean.setSpice (spice); FileOutputStream fout = new FileOutputStream (safeDepositBox); ObjectOutputStream oos = new ObjectOutputStream (fout); oos.writeObject (upsBean); oos.flush(); oos.close(); // do stuff with spice and if it works, ... // contextInitialized (null); } // Above is working currently, would like the following to work public void contextDestroyed(ServletContextEvent sce) { System.setProperty ("spice", spice); System.out.println ("[DBInterface" + dbiIndex + "] Spice saved at " + DateFormat.getDateTimeInstance (DateFormat.SHORT, DateFormat.LONG).format (new Date())); } public void contextInitialized(ServletContextEvent sce) { if (sce != null) { spice = System.getProperty ("spice"); System.out.println ("[DBInterface" + dbiIndex + "] Spice retrieved at " + DateFormat.getDateTimeInstance (DateFormat.SHORT, DateFormat.LONG).format (new Date())); } // do stuff with spice } ============ UPSBean.java: ============ public class UPSBean implements Serializable { private String spice = "parsley, sage, rosemary and thyme"; public UPSBean() { } public String getSpice() { return spice; } public void setSpice (String s) { spice = s; } } 

我试图看看get / setProperty是否在上面工作。 试图直接使用JNDI,但是当我在contextDestroyed()中使用资源MyServlet / upsBean设置setSpice并尝试在contextInitialized()中读取它时,我得到一个null(抱歉,我已经删除了那部分代码)。 现在context.xml中的声明以及resource-env-ref已经变得多余。 当前的解决方法是将序列化实例保存在数据文件中(不是很好)。

如果你想以编程方式处理它,我认为你可能正在寻找的是ServletContextListener 。 创建一个实现接口的类,并在contextInitialized(ServletContextEvent sce) -method中编写所需的function,请参见此处的简单示例。

Tomcat在这里没有提供任何帮助。

这是个坏消息。

好消息是……这是一个流程问题,可以使用servlet-api提供的工具以及Tomcat可以提供的一些其他项来解决。 这是成分..

  • Tomcat在容器启动时提供了使用Listener的能力 – 通过Listener元素。 使用这些来跟踪容器启动,关闭等的时间。
  • servlet api提供ServletContextListener来监听webapp启动,关闭
  • 当tomcat启动时 – 您可以通过首先设置JAVA_OPTS来传递系统属性。 但要小心 – 可以使用ps命令发现这些值。
  • 根据您的部署方法 – 您可以通过my-app.xml添加配置设置(其中my-app是部署应用程序的名称)
  • 在CATALINA_BASE / conf / context.xml中 – 您还可以为webapp设置init / env参数,以供所有Web应用程序查看。
  • 如果您不使用安全管理器 – 您应该能够设置/清除某些系统属性。

鉴于以上所有 – 您可以从文件中读取“受保护”值并将其写入系统属性。 然后,为了增加保护,以防您不希望值始终作为系统属性存在 – 您可以使用ServletContextListener读取系统属性值并从系统属性中删除它们。 然后在重新部署时 – 侦听器会在关闭时写入重置系统属性 – 所以当webapp重新启动时 – 它们仍然存在。

所以启动顺序就是这样

  • 管理员输入pw(现有流程)并保存文件
  • 在webapp启动时 – ServletContextListner查找文件。 如果存在 – 使用这些值
  • 在webapp shutdown上 – 将值写入System.properties
  • 在webapp restart上 – ServletContextListner看到文件丢失并使用System.properties。 然后删除system.properties

有了上述内容 – 您可以重新部署,而不是将密码写入磁盘,并希望通过临时存储为system.properties来最小化任何攻击向量。

祝你好运。