【问题标题】:Hide datasource password in spring xml file在spring xml文件中隐藏数据源密码
【发布时间】:2011-05-10 22:33:07
【问题描述】:

有没有办法在 xml spring 配置文件中隐藏/加密密码? 我读到这可以通过 DataSource 的“自定义”子类来实现,但是解决方案将密钥与纯文本一样保存在相同的配置文件中......所以有点没用。

有办法为此使用 KeyStore 吗? 例如从密钥库中读取值。

谢谢大家。

【问题讨论】:

    标签: java xml spring keystore password-encryption


    【解决方案1】:

    隐藏密码的目的是什么?我建议您在容器中配置数据源(Tomcat、JBoss 或任何您使用的)并使用 jndi 将数据源注入您的应用程序:

    <jee:jndi-lookup id="thedatasource"
                         jndi-name="java:comp/env/jdbc/thedatasource"
                         lookup-on-startup="false"
                         expected-type="javax.sql.DataSource"/>
    

    这样您就不必在应用程序中公开和密码,而只需在 servlet 容器中。

    【讨论】:

    • 我正在开发一个桌面客户端-服务器应用程序,我想保护应用程序数据库。我想成为独一无二的,可以触动我的数据库。
    • 正确,对于批处理应用程序,您将不得不采取不同的方法。
    • 简单地说:只需创建一个 JndiObjectFactoryBean 类的 bean 作为您的数据源 bean。 &lt;bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"&gt; &lt;property name="jndiName" value="myDataResourceName" /&gt; &lt;/bean&gt;
    【解决方案2】:

    是的,你可以这样做。您必须围绕数据源类创建一个包装器 bean。这是我以前如何做的一个例子。希望这会有所帮助!

    <beans>
        <bean id="someDao" class="com.dao.SomeDAOImpl">
             <property name="datasource">
                <ref local="secureDataSource"/>
            </property>
        </bean>
        <bean id="secureDataSource" class="com.ds.SecureDataSource">
            <property name="driverClassName">
                <value><your driver></value>
            </property>
            <property name="url">
                <value><your url></value>
            </property>  
            <property name="username">
                <value><your user id></value>
            </property>
            <property name="password">
                <value><encrypted_pwd></value>
            </property> 
        </bean> 
    </beans>
    

    然后您需要在 SecureDataSource 类中解密密码。

    import java.sql.Connection;
    import java.sql.SQLException;
    
    
    public class SecureDataSource extends DriverManagerDataSource{
    
        private String url;
        private String username;
        private String password;
        /**
         * @param url the url to set
         */
        public void setUrl(String url) {
            this.url = url;
        }
    
        /**
         * @param username the username to set
         */
        public void setUsername(String username) {
            this.username = username;
        }
    
        /**
         * @param password the password to set
         */
        public void setPassword(String password) {
            this.password = password;
        }
    
        protected Connection getConnectionFromDriverManager() throws SQLException {
            String decryptedPassword = null;
            //decrypt the password here
            return getConnectionFromDriverManager(url,username,decryptedPassword);
        }
    }
    

    【讨论】:

    • 如果您使用装饰器模式,这将更加灵活(例如,您的数据源包含另一个数据源并将除密码之外的所有内容转发给它)
    • 触摸 S.P.!这是一个快速表明它可以完成的方法。但是 OP 可以按照他/她想要的方式重新设计它。
    • @SeanPatrickFloyd 您是否有任何指针提供使用装饰器模式的示例?谢谢。
    • @MarcdeVerdelhan 例如 Spring 的 DelegatingDataSource。它将所有方法委托给一个包装好的数据源,除非你重写它们。
    • @blow - 它提供的唯一东西是错误的安全性。首先,解密密钥必须对应用程序可用,并且密钥 + 加密文本 = 纯文本。其次,您可以在连接到数据库后立即进行堆转储并在那里找到密码。第三,您可以嗅探网络流量并从中获取密码。您无法以安全的方式执行此操作。
    【解决方案3】:

    已经给出了很好的选择,另一个明显的答案是使用PropertyPlaceholderConfigurer

    <context:property-placeholder
        system-properties-mode="OVERRIDE" 
        location="classpath:database.properties" />
    
    <bean id="dataSource" class="com.whatever.datasource.you.Use">
        <property name="password" value="${database.password}" />
    </bean> 
    

    现在,您可以将密码作为属性文件保存在属性文件中(如果您不想在 SCM 中拥有它,可以在部署期间创建)或系统属性(希望它也无法访问)其他开发人员)。

    澄清: 在部署期间创建有点模糊。我猜你将不得不编写一个安装程序,在最终用户的机器上动态生成属性文件,可能还要加上注册/登录机制。


    编辑:我还没有弄清楚你在向谁隐藏信息。两种理论:

    a) 有权访问您的源代码的人
    b) 你的客户

    如果是a),那就走我的路吧。其他开发人员只需使用调试器启动您的应用程序就可以轻松破坏所有其他方式(突然他进入数据源对象并看到密码)。

    如果是 b),那么基本上你就没有机会了。客户有很多方法可以获取您的密码:调试器、代理、字节码操作、加载时间编织等。即使他不这样做,他也只需附加一个端口嗅探器即可清楚地获取密码文本。唯一安全的做法是为每个客户设置一个用户名/密码(切勿在客户的机器上存储全局密码)。

    【讨论】:

    • @S.P.Floyd - seanizer:我想我不明白这一点。这样一来,密码就存储在database.properties中,所以任何人都可以读取它,不是吗?
    • 如果 database.properties 是例如在部署期间动态创建。
    • “部署”到底是什么意思?部署是当我向用户发布我的软件时,你是说这个吗?
    • @blow: S.P. 意味着在构建/打包项目期间,您可以有一个任务来生成此属性文件。
    • 如果我在打包过程中生成此文件,任何人都可以打开放入我的包中的文件...嗯,我想我不明白你的意思。
    【解决方案4】:

    我最近也有同样的问题。我想将密码的散列版本存储在 .properties 文件中。 多亏了前面的选项,我成功了:我扩展了 DelegatingDataSource 并覆盖了 getConnection([...]) 方法。

    public class UnhashingDataSource extends DelegatingDataSource {
    
        private static final Logger LOGGER = Logger.getLogger(UnhashingDataSource.class);
        private static final int HEX_RADIX = 16;
        private static final String DB_PASS = "a_sample_password";
    
        @Override
        public Connection getConnection() throws SQLException {
            DriverManagerDataSource dataSource = (DriverManagerDataSource) getTargetDataSource();
            return getConnection(dataSource.getUsername(), dataSource.getPassword());
        }
    
        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            try {
                DataSource datasource = getTargetDataSource();
                if (datasource == null) {
                    throw new RuntimeException("targetDataSource is null");
                }
                MessageDigest md = MessageDigest.getInstance("SHA-1");
                md.reset();
                md.update(DB_PASS.getBytes());
                if (password.equals(getHexString(md.digest()))) {
                    return datasource.getConnection(username, DB_PASS);
                } else {
                    throw new RuntimeException("Unable to connect to DB");
                }
            } catch (NoSuchAlgorithmException e) {
                LOGGER.error("Unknown algorithm");
            }
            return null;
        }
    
        private String getHexString(final byte[] messageDigest) {
            BigInteger bigInt = new BigInteger(1, messageDigest);
            return bigInt.toString(HEX_RADIX);
        }
    }
    

    然后,这是我在applicationContext.xml 中使用它的方式:

    # Using the unhashing datasource
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="unhashingDataSource" />
        # ...
    </bean>
    <bean id="hashedDataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${datasource.driverClassName}" />
        <property name="url" value="${datasource.url}" />
        <property name="username" value="${datasource.username}" />
        <property name="password" value="${datasource.hash}" />
    </bean>
    <bean id="unhashingDataSource"
        class="my.package.UnhashingDataSource">
        <property name="targetDataSource" ref="hashedDataSource" />
    </bean>
    

    datasource.hash 是一个属性(来自 .properties 文件),存储方式如下:

    datasource.hash = 2e54b0667ef542e3398c55a08a4e04e69b9769e8
    

    普通密码仍然在字节码中,但不再直接在 .properties 文件中。

    【讨论】:

      【解决方案5】:

      感谢您的所有帖子和查询。

      希望访问者通过阅读此页面了解加密密码的技术方法。我想在这里补充一件重要的事情,如果您正在处理生产,那么肯定会建议您使用任何“安全哈希算法”,例如带有盐的 SHA-256。您可以考虑使用盐作为行业标准的安全哈希算法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-09-21
        • 2010-10-18
        • 1970-01-01
        • 2013-04-04
        • 2017-03-12
        • 2011-09-14
        • 1970-01-01
        相关资源
        最近更新 更多