欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring学习笔记(五)——配置信息进阶篇

程序员文章站 2022-05-08 16:16:59
...

前提

这篇博文是这套Spring学习笔记的第五篇——配置信息进阶篇,这一篇的主要内容包括配置信息与配置文件的脱离和配置信息的加密。如果需要了解有关Spring的综述信息或博文的索引信息,请移步:
《综述篇》


当前的配置情形

说道配置信息,我们最常见的就是dataSource数据源的配置,目前的配置方式形如:

以下为applicationContext.xml文件中的部分代码

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
          destroy-method="close" 
          p:driverClassName="com.mysql.jdbc.Driver"
          p:url="jdbc:mysql://localhost:3306/myfirstapp?characterEncoding=utf8" 
          p:username="root"
          p:password="" />

现在问题来了,当我们的工程变得复杂起来的时候,如果有十个甚至一百个配置文件,里面都需要这段配置信息。如果仍然这样配置,那么一旦发生改动,就需要把每个配置文件里的配置信息都改了,这其中难免发生遗漏且工作量巨大。


数据与代码的分离

上述的配置代码*有四行可以提出来的数据:

com.mysql.jdbc.Driver
jdbc:mysql://localhost:3306/myfirstapp?characterEncoding=utf8
root
(空字符串)

为了易于维护,我们应该把这些数据与代码分离开来,仅需要改数据的时候不影响代码文件。

以下分步骤介绍把数据从代码中分离出来的过程

创建属性文件

依次右键点击源包->新建->属性文件
Spring学习笔记(五)——配置信息进阶篇

命名为 jdbc 并点击完成
Spring学习笔记(五)——配置信息进阶篇

写入配置信息

上一步完成后,可以看见源包下面多出来一个jdbc.properties文件
Spring学习笔记(五)——配置信息进阶篇

双击打开它,填入以下数据

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/myfirstapp?characterEncoding=utf8
username=root
password=

注意:
① 每一条配置信息以键值对方式存储,键与值间用等号隔开;
② 值的两边没有双引号。

引入属性文件

配置文件中想要调用到这些配置信息,需要先引入上述的jdbc.properties文件

注意:以下代码需添加到applicationContext.xml文件中

    <context:property-placeholder location="classpath:jdbc.properties"/>

引用配置信息

在配置文件中,需要引用配置信息时,用 ${键} 的方式

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
          destroy-method="close" 
          p:driverClassName="${driverClassName}"
          p:url="${url}" 
          p:username="${username}"
          p:password="${password}" />

这样,就做到了数据与代码的分离,以后需要改数据源的配置信息时,只需要改jdbc.properties这个属性文件就可以了。


加密敏感信息

在属性文件中,例如driverClassName和url这种属性无所谓安全性,但是数据库的账号密码username和password就相对敏感的多,一般情况下,我们不希望别人可以不加限制的看到这些信息,因此需要对敏感信息进行加密。

但是有一个问题,加密后的信息必须在程序运行的时候解密出来才能正常访问数据库,这个解密的过程由谁来做呢?

一般的属性配置

上述引入属性文件的步骤中,我们使用了<context>标签,还有一种设置匿名Bean的方式与之等效

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
          p:location="classpath:jdbc.properties"
          p:fileEncoding="utf-8"/>

可以看到,为该指定的类为PropertyPlaceholderConfigurer,从字面上理解即“属性占位符配置器”,我们想要对属性进行加解密,就要从这里入手。

自定义配置器

因为默认的配置器不会对属性做任何操作,所以我们需要自定义一个配置器

首先假定有一个可以提供某对称加密算法的加解密功能的类Secret,我们创建一个名为SensitivePropertyPlaceholderConfigurer,即敏感信息…

public class SensitivePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {  //注①

    private static final Secret SECRET = new Secret();  //注②
    private static final Set<String> SENSITIVE_PROPERTIES = new HashSet<String>() {  //注③
        {
            add("username");
            add("password");
        }
    };

    @Override  //注④
    protected String convertProperty(String propertyName, String propertyValue) {
        if (SENSITIVE_PROPERTIES.contains(propertyName)) {  //注⑤
            return SECRET.decode(propertyValue);
        }
        return propertyValue;
    }
}

注释:
①这里让我们自定义的Configurer继承了PropertyPlaceholderConfigurer;
②定义对称加密算法类的对象;
③定义敏感信息(键);
④这里重写了convertProperty这个方法,我们就是要通过这个方法来对文件中读取到的已经加密的属性进行解密,也可以想到,默认配置器PropertyPlaceholderConfigurer中,这里是不做任何操作的。
⑤判断逻辑是:如果读取到的属性的键是被定义的敏感信息,那么返回解密后的属性,否则原文返回。

加密敏感属性

最后一步,我们对username和password进行加密后存到jdbc.properties文件中

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/myfirstapp?characterEncoding=utf8
username=7uu0IDu+ZhU=
password=Au09ghtC+bSgecSYajZmjg==

这样任何人都不能直接看到我们数据库的账户和密码了。


后记

上述代码中,我特意把Secret类做了抽象的模糊定义,意思是只要是具有加密和解密功能的对称加密算法,都是可以的。 这里给出一份用DES算法实现的DESUtil的代码,作为参考:

public class DESUtil {

    private static Key key;
    private static final String SECRET_KEY = "SuperUser";  //注①

    static {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
            keyGenerator.init(new SecureRandom(SECRET_KEY.getBytes()));
            key = keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public String encode(String plainText) {
        Encoder encoder = Base64.getEncoder();
        try {
            byte[] palinTextByte = plainText.getBytes("UTF8");
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] cipherTextByte = cipher.doFinal(palinTextByte);
            return encoder.encodeToString(cipherTextByte);
        } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
            throw new RuntimeException(e);
        }
    }

    public String decode(String cipherText) {
        Decoder decoder = Base64.getDecoder();
        try {
            byte[] cipherTextByte = decoder.decode(cipherText);
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] plainTextByte = cipher.doFinal(cipherTextByte);
            return new String(plainTextByte, "UTF8");
        } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e) {
            throw new RuntimeException(e);
        }
    }
}

注释:
①这个**很重要,用一个**加密,就必须用同一个**来解密,**可以换成任何你想设置的字符串;
②引入包的代码没有贴出来,针对程序中的Encoder和Decoder请分别引入:
import java.util.Base64.Encoder;
import java.util.Base64.Decoder;
③对属性进行加密时,可以给这个类写一个main函数,调用一下encode函数,把输出的密文填到属性文件中即可。