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

时间:2011-02-11 17:32:37

标签: java tomcat servlets

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

在初始化期间,

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

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

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

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

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

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

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

MS编辑:

==========
restart.pl:
==========
ReadMode 2; print "\nEnter spice: "; chomp ($spice = <STDIN>); 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
===========
    <Resource name="MyServlet/upsBean" auth="Container" type="packageName.UPSBean"
                factory="org.apache.naming.factory.BeanFactory" readOnly="false"/>

===================
app/WEB-INF/web.xml
===================
  <listener>
        <listener-class>packageName.DBInterface</listener-class>
  </listener>
  <resource-env-ref>
    <description>Use spice as transferable during redeployment</description>
    <resource-env-ref-name>MyServlet/upsBean</resource-env-ref-name>
    <resource-env-ref-type>packageName.UPSBean</resource-env-ref-type>
  </resource-env-ref>

==============
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已经变得多余。目前的解决方法是将序列化实例保存在数据文件中(不是很好)。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

这是个坏消息。

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

  • Tomcat通过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重启时 - ServletContextListner看到文件丢失并使用System.properties。然后删除system.properties

通过上述内容 - 您可以在不将密码写入磁盘的情况下进行重新部署,并希望通过临时存储为system.properties来最小化任何攻击媒介。

祝你好运。

答案 4 :(得分:1)

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资源侦听器实现,并且在您的单元测试中,您可能需要执行一些额外的步骤以确保您的代码可以进行单元测试。

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

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

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

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

选项3:使用JMX

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

希望这会有所帮助。