zoukankan      html  css  js  c++  java
  • 基于spring的placeholder思路处理配置信息敏感信息加密解密的实践

    基于Spring的placeholder处理思路,实现系统配置信息敏感信息的加密解密处理。

    我们的处理方案,是基于类org.springframework.beans.factory.config.PropertiesFactoryBean进行重写,嵌入密文信息的解密逻辑,灵活处理各种敏感信息的加解密,而且加解密算法,可以根据需要自己灵活设计。

    1. 首先,设计敏感信息的加解密算法程序,这里,就基于JDK自带的工具,基于AES算法进行加密encrypt和解密dencrypth操作

    package com.tk.robotbi.core.engine;
    
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.KeyGenerator;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    
    import org.apache.commons.codec.DecoderException;
    import org.apache.commons.codec.binary.Hex;
    
    /**
     * @author shihuc
     * @date  2018年3月8日 上午9:20:55
     * 
     * 通过JDK自带的AES加解密方法,对系统配置参数需要防范的信息进行处理
     * 
     */
    public class JdkAesHelper {
        
        private static String ALGORITHM = "AES";
        private static String TRANSFORMATION = "AES/ECB/PKCS5Padding";
        private static int KEY_SIZE = 128;
        
        /*
         * 这里的seed值,有点类似加密中的干扰项,使得密码更加不容易破解,可以当作私钥信息使用,即不知道这个信息,AES解密也无法进行。
         */
        private static String SEED = "xxxxxxxx***************";
        
        public static String doEncrypt(String toEnc, String seed) {
            // 生成密钥  
            KeyGenerator keyGenerator = null;
            try {
                keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } 
            System.out.println(keyGenerator.getProvider());  
            /*
             * 指定key的长度为128位
             * 给定种子作为随机数初始化出。若不指定种子信息,则秘钥生产器初始化将会完全随机,最后无法解密
             */
            SecureRandom random = null;
            try {
                random = SecureRandom.getInstance("SHA1PRNG");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            random.setSeed(seed.getBytes());
            keyGenerator.init(KEY_SIZE, random);
            SecretKey secretKey = keyGenerator.generateKey();  
            byte[] bytesKey = secretKey.getEncoded();  
    
            // key转换  
            SecretKeySpec key = new SecretKeySpec(bytesKey, ALGORITHM);  
            
            // 加密  
            Cipher cipher = null;
            byte[] result = null;
            try {
                cipher = Cipher.getInstance(TRANSFORMATION);
                cipher.init(Cipher.ENCRYPT_MODE, key);            
                result = cipher.doFinal(toEnc.getBytes());            
            } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
                e.printStackTrace();  
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (IllegalBlockSizeException | BadPaddingException e) {
                e.printStackTrace();
            }        
            return Hex.encodeHexString(result);
        }
        
        public static String doDecrypt(String toDec, String seed){
            // 生成密钥  
            KeyGenerator keyGenerator = null;
            try {
                keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            System.out.println(keyGenerator.getProvider());  
            /*
             * 指定key的长度为128位
             * 给定种子作为随机数初始化出。若不指定种子信息,则秘钥生产器初始化将会完全随机,最后无法解密
             */
            SecureRandom random = null;
            try {
                random = SecureRandom.getInstance("SHA1PRNG");
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            random.setSeed(seed.getBytes());
            keyGenerator.init(KEY_SIZE, random);         
            SecretKey secretKey = keyGenerator.generateKey();  
            byte[] bytesKey = secretKey.getEncoded();  
    
            // key转换  
            SecretKeySpec key = new SecretKeySpec(bytesKey, ALGORITHM);  
            // 解密  
            Cipher cipher = null;
            byte[] result = null;
            try {
                cipher = Cipher.getInstance(TRANSFORMATION);
                cipher.init(Cipher.DECRYPT_MODE, key);
                byte rawHex[] = Hex.decodeHex(toDec);                        
                result = cipher.doFinal(rawHex);  
            } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
                e.printStackTrace();  
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (IllegalBlockSizeException | BadPaddingException e) {
                e.printStackTrace();
            } catch (DecoderException e) {
                e.printStackTrace();
            }        
            return new String(result);                
        }
        
        public static void main(String args[]){
            String inPass = "二师兄到底是帅啊!";
            System.out.println("加密之前:" + inPass);
            String ouPass = JdkAesHelper.doEncrypt(inPass, SEED);
            System.out.println("加密之后:" + ouPass);
            String revPass = JdkAesHelper.doDecrypt(ouPass, SEED);
            System.out.println("解密之后:" + revPass);
        }
    
        /**
         * @return the sEED
         */
        protected static String getSEED() {
            return SEED;
        }
    }

    2. 接下来,就是PropertiesFactoryBean类的重写逻辑

    package com.tk.robotbi.core.engine;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    import java.util.Properties;
    
    import javax.annotation.PostConstruct;
    
    import org.springframework.beans.factory.config.PropertiesFactoryBean;
    
    /**
     * @author shihuc
     * @date  2018年3月8日 上午8:58:23
     * 
     * 对系统配置参数,里面涉及到的加密了的信息,在实际使用之前,进行解密恢复明文使用,实现软件包静态传递的时候,不至于泄露重要的敏感信息。例如数据库的密码。
     * 注意:1. 此方法是基于PropertiesFactoryBean的扩展,方便配置文件里的参数,通过注解的方式在java文件中直接使用。
     *     2. 推荐使用此种方法。
     */
    public class MyPropertiesFactoryBean extends PropertiesFactoryBean {
        
        /*
         * 需要解密的参数列表
         */
        private List<String> decryptParams;
            
        @PostConstruct
        public void init(){        
            if(decryptParams == null){
                try {
                    throw new Exception("parameter container initialization error");
                } catch (Exception e) {            
                    e.printStackTrace();
                }
            }        
        }
        
        /**
         * 重写属性加载函数mergeProperties(),在这个函数里面,将待解密的参数找出来,然后进行指定的解密函数操作。
         * 注意: 这里是静态的,默认都用一种解密算法进行,后期可以灵活的配置,实现不同的参数采用不同的解密算法,或者算法可以配置。
         */
        @Override
        protected Properties mergeProperties()    throws IOException {
            Properties result = super.mergeProperties();
            
            /*
             * 将配置过程中带入的信息首尾空格去掉,否则导致解密失败
             */
            List<String> params = new ArrayList<String>();
            for(String param: this.decryptParams){
                String newParam = param.trim();
                params.add(newParam);
            }
            
            Enumeration<?> keys = result.propertyNames();  
            while (keys.hasMoreElements()) {  
                String key = (String)keys.nextElement();  
                String value = result.getProperty(key);  
                if (params.contains(key) && null != value) {  
                    result.remove(key);
                    value = JdkAesHelper.doDecrypt(value.trim(),JdkAesHelper.getSEED());  
                    result.setProperty(key, value);  
                } 
            } 
            return result;
        }
    
        /**
         * @return the decryptParams
         */
        public List<String> getDecryptParams() {
            return decryptParams;
        }
    
        /**
         * @param params the decryptParams to set
         */
        public void setDecryptParams(List<String> params) {
            this.decryptParams = params;        
        }
    }

    3. 最后,就是spring的配置文件的修改

    默认的基于PropertiesFactoryBean的配置信息如下:

    <bean id="configRealm" class="org.springframework.beans.factory.config.PropertiesFactoryBean">    
        <property name="locations">
            <list>
                <value>classpath:conf/jdbc.properties</value>
                <value>classpath:conf/mongo.properties</value>
                <value>classpath:conf/redis.properties</value>                
                <value>classpath:conf/params.properties</value>
                <value>classpath:conf/elasticsearch.properties</value>
                <value>classpath:conf/fileUpload.properties</value>
            </list>
        </property>
    </bean>

    改用重写后的PropertiesFactoryBean的配置信息如下:

    <bean id="configRealm" class="com.tk.robotbi.core.engine.MyPropertiesFactoryBean">
        <property name="decryptParams">
            <list>
                <value>mongo.write1.password </value>
                <value>mongo.read1.password </value>
                <value>mongo.write2.password</value>
                <value>mongo.read2.password</value>
            </list>
        </property>
        <property name="locations">
            <list>
                <value>classpath:conf/jdbc.properties</value>
                <value>classpath:conf/mongo.properties</value>
                <value>classpath:conf/redis.properties</value>                
                <value>classpath:conf/params.properties</value>
                <value>classpath:conf/elasticsearch.properties</value>
                <value>classpath:conf/fileUpload.properties</value>
            </list>
        </property>
    </bean>

    基于上述几步之后,就需要将配置文件里面出现在decryptParams列表的参数用加密后的信息写入相应的配置文件,这里,我们将mongo数据库的密码进行加密写入mongo.properties,有两组的读写库密码。运行程序,启动正常,数据能够连接上。一切ok。

    这里,需要注意的有下面几点:

    1. spring的配置文件中,参数值后面或者前面的空格,spring加载参数的时候,是不会给trim掉的,必须自己在应用逻辑中处理。

    2. spring的配置文件中,bean的list类型的成员变量,类型不同,配置方式不同。

    基本类型的,可以采用如下的形式配置:
    <list>
      <value>aaa</value>
      <value>bbb</value>
      <value>ccc</value>
    </list>
    若bean的list类型成员变量,不是基本类型,而是具体的bean,那么需要采用下面的形式配置:
    <list>
      <ref bean="bean1"/>
      <ref bean="bean2"/>
      <ref bean="bean3"/>
    </list>

    3. spring的配置文件中,list的类型的参数配置,在bean的定义中,成员变量,可以定义成数组,也可以定义成List。

    例如本加解密案例中,MyPropertiesFactoryBean的实现过程中,private List<String> decryptParams;的定义,可以改成private String[] decryptParams;效果是一样的。只要注意响应的setter和getter方法做适当的调整即可。


    4. 继承PropertiesFactoryBean,重写函数mergeProperties(),与继承org.springframework.beans.factory.config.PropertyPlaceholderConfigurer然后重写processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)都可以达到类似的效果。只是建议采用继承PropertiesFactoryBean的方式,方便参数通过注解的形式在java程序中直接引用参数值。
    不管是继承PropertiesFactoryBean的方案还是继承PropertyPlaceholderConfigurer的方案,其实最终关注到的核心都是来自共同的父类PropertiesLoaderSupport中的properties的加载逻辑前后的处理。都可以通过修改mergeProperties()函数的逻辑实现。当然也可以按照各自的需要进行调整相应函数的重写逻辑。

    PS:附上继承org.springframework.beans.factory.config.PropertyPlaceholderConfigurer然后重写processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)的实现逻辑:

    package com.tk.robotbi.core.engine;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    import java.util.Properties;
    
    import javax.annotation.PostConstruct;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
    
    /**
     * @author shihuc
     * @date  2018年3月8日 下午1:01:22
     * 
     * 对系统配置参数,里面涉及到的加密了的信息,在实际使用之前,进行解密恢复明文使用,实现软件包静态传递的时候,不至于泄露重要的敏感信息。例如数据库的密码。
     * 注意:1. 此种方法是对PropertyPlaceholderConfigurer的扩展,实现思路直接。
     *     2. 较常用,但是不便于配置文件中的参数通过注解的方式在java文件中直接使用。
     * 
     */
    public class MyPropertiesPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
    
        /*
         * 需要解密的参数列表
         */
        private List<String> decryptParams;
        
        @PostConstruct
        public void init(){
            if(decryptParams == null){
                try {
                    throw new Exception("parameter container initialization error");
                } catch (Exception e) {            
                    e.printStackTrace();
                }
            }
        }
        
        
        /**
         * 重写属性处理函数,在这个函数里面,将待解密的参数找出来,然后进行指定的解密函数操作。
         * 注意: 这里是静态的,默认都用一种解密算法进行,后期可以灵活的配置,实现不同的参数采用不同的解密算法,或者算法可以配置。
         */
        protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {        
            Enumeration<?> keys = props.propertyNames(); 
            
            /*
             * 将配置过程中带入的信息首尾空格去掉,否则导致解密失败
             */
            List<String> params = new ArrayList<String>();
            for(String param: this.decryptParams){
                String newParam = param.trim();
                params.add(newParam);
            }
            
            while (keys.hasMoreElements()) {  
                String key = (String)keys.nextElement();  
                String value = props.getProperty(key);  
                if (params.contains(key) && null != value) {  
                    props.remove(key);
                    value = JdkAesHelper.doDecrypt(value.trim(),JdkAesHelper.getSEED());  
                    props.setProperty(key, value);  
                }  
                System.setProperty(key, value);  
            }  
            super.processProperties(beanFactoryToProcess, props);
        }
    
    
        /**
         * @return the decryptParams
         */
        public List<String> getDecryptParams() {
            return decryptParams;
        }
    
    
        /**
         * @param decryptParams the decryptParams to set
         */
        public void setDecryptParams(List<String> decryptParams) {
            this.decryptParams = decryptParams;
        }
        
    }
  • 相关阅读:
    Android Studio 字体和字号调整
    【IDEA】项目中引入Spring MVC
    【Double】double精度问题和int、long除不尽取舍问题
    【进制转换】原码反码和补码的理解以及进制转换
    【工具】SwitchHost的使用
    【工具】谷歌浏览器使用技巧
    【Git和GitHub】学习笔记
    【IE兼容性】代码中多语言样式+IE不兼容解决
    【Trello】使用指南
    【实操】进制转换:除基倒取余法
  • 原文地址:https://www.cnblogs.com/shihuc/p/8528571.html
Copyright © 2011-2022 走看看