几年前研究过Tomcat context.xml 中数据库密码改为密文的内容,因为当时在客户云桌面代码没有留备份也没有文章记录,最近项目又提出了这个需求就又重新拾起来学习一下。在网上找了一些资料,自己也大概试了一下,目前功能是实现了。
参考链接:
https://blog.csdn.net/fzzsh/article/details/8863338
https://blog.csdn.net/T_P_F/article/details/118552417
Tomcat 常见情况
正常的tomcat context.xml配置文件数据库用户名和密码都是明文的
<Resource name="jdbc/DSamdb" auth="Container" type="javax.sql.DataSource" description="test DB"
        initialSize="10" 
        maxWaitMillis="10000" maxTotal="80" maxIdle="20" minIdle="10" validationQuery="select 1"
        username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/webtest?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false"         
   />现在因为安全要求或者国家等保要求,这个数据库密码是需要加密的,并且要定期修改的,首先实现加密功能
Tomcat context.xml密码加密
tomcat加密是通过在配置文件中增加 factory参数让数据源的解析指到定制化的jar包中,对密码或用户名进行解析,加密后的配置文件信息
<Resource name="jdbc/DSamdb" auth="Container" type="javax.sql.DataSource" description="test DB"
        initialSize="10" 
        maxWaitMillis="10000" 
        maxTotal="80" maxIdle="20" minIdle="10" validationQuery="select 1"
        factory="com.axb.data.factory.DataSourceFactory"
        username="root"
        password="726f6f74" 
        driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/webtest?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false"         
   />factory="com.axb.data.factory.DataSourceFactory" 是包名+类名。password 的root已经加密为“726f6f74”,从网上找到加密算法比较简单,所以加密串看着也挺简单。
配置信息啰嗦完了,上代码:
DataSourceFactory.java 继承BasicDataSourceFactory
package com.axb.data.factory;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.DataSource;
import net.sf.json.JSONObject;
import org.apache.tomcat.dbcp.dbcp.BasicDataSource;
import org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory;
import com.axb.data.factory.util.Encode;
import com.axb.data.factory.util.HttpUtil;
public class DataSourceFactory extends BasicDataSourceFactory {
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
            Hashtable environment) throws Exception {
        if (obj instanceof Reference) {
            //用户名没有加密
//            setUsername((Reference) obj);
            setPassword((Reference) obj);
        }
        return super.getObjectInstance(obj, name, nameCtx, environment);
        
    }
    private void setUsername(Reference ref) throws Exception {
        findDecryptAndReplace("username", ref);
    }
    private void setPassword(Reference ref) throws Exception {
        findDecryptAndReplace("password", ref);
    }
    private void findDecryptAndReplace(String refType, Reference ref)
            throws Exception {
        int idx = find(refType, ref);
        System.out.println(idx +"----->findDecryptAndReplace---" +ref.get(idx));
        String decodeStr = ref.get(idx).getContent().toString();
        System.out.println("findDecryptAndReplace---" +decodeStr);
        String decrypted = Encode.decode(decodeStr);
        
        replace(idx, refType, decrypted, ref);
       
    }
    private int find(String addrType, Reference ref) throws Exception {
        Enumeration enu = ref.getAll();
        for (int i = 0; enu.hasMoreElements(); i++) {
            RefAddr addr = (RefAddr) enu.nextElement();
            if (addr.getType().compareTo(addrType) == 0) {
                return i;
            }
        }
        throw new Exception("The \"" + addrType
                + "\" name/value pair was not found"
                + " in the Reference object. The reference Object is" + " "
                + ref.toString());
    }
    private void replace(int idx, String refType, String newValue, Reference ref)
            throws Exception {
        ref.remove(idx);
        ref.add(idx, new StringRefAddr(refType, newValue));
    }
   }
加密类,可使用自己的加密算法,主要修改encode和decode两个方法,自己可以写个main方法测试加解密功能。代码也是从CSDN找的,连接:
package com.axb.data.factory.util;
public class Encode {
    //加密
        public static String encode(String password) {
            String result = "";
            byte[] psd = password.getBytes();
            for (int i = 0; i < psd.length; i++) {
                result += Integer.toHexString(psd[i] & 0xff);
            }
            return result;
        }
        //解密
        public static String decode(String password) {
            String result = "";
            System.out.println("encode---->" +password);
            password = password.toUpperCase();
            int length = password.length() / 2;
            char[] hexChars = password.toCharArray();
            byte[] d = new byte[length];
            for (int i = 0; i < length; i++) {
                int pos = i * 2;
                d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
            }
            result = new String(d);
            return result;
        }
        //字符转字节
        public static byte charToByte(char c) {  
            return (byte) "0123456789ABCDEF".indexOf(c);  
        }tomcat启动后控制台输出效果:
 
   能正常启动就可以,该方法不需要额外的jar包,开发的时候只需要引入tomcat-dbcp.jar,这个包在tomcat的lib下就有。不需要额外下载。
通过http请求密码
这样改完以后配置文件密码是密文了,但是每次更换密码还是需要修改密文并重启tomcat,当然也可以把密码写在某个路径下的文件中。运维人员每次变更密码都要改,服务器多了也是需要花费不少时间。我就试着用http请求获取密码,改动是在findDecryptAndReplace 方法
private void findDecryptAndReplace(String refType, Reference ref)
            throws Exception {
        int idx = find(refType, ref);
        System.out.println(idx +"----->findDecryptAndReplace---" +ref.get(idx));
        String decodeStr = ref.get(idx).getContent().toString();
        System.out.println("findDecryptAndReplace---" +decodeStr);
        String decrypted = Encode.decode(decodeStr);
        
//        replace(idx, refType, decrypted, ref);
        
        /**
         * 试试远程获取密码
         */
        String url = "http://localhost:9002/getDataBaseInfo";
        String jsonData = "{\"userId\":\"zhangsan\"}";
        String returnStr  = HttpUtil.httpPost(url,jsonData);
        JSONObject tokenJSON = JSONObject.fromObject(returnStr);
        System.out.println("decrypted:==="+decrypted +"json_password"+tokenJSON.get("password"));
        System.out.println(tokenJSON.get("password")+"===="+tokenJSON.get("userName"));
        replace(idx, refType, tokenJSON.get("password").toString(), ref);
       
    }启动后控制台日志:
 
   http请求需要多引入一些jar包:
 
   jar包直接扔到tomcat的lib目录下就可以。
测试方法类也简单
@RequestMapping(value = "/getDataBaseInfo", method = RequestMethod.POST)
    public String getDataBaseInfo(@RequestBody String josnString) {
        System.out.println("=================josnString="+josnString);
        Map m = new HashMap();
        m.put("userName","root");
        m.put("password","root");
        Gson gson = new Gson();
        gson.toJson(m);
        return gson.toJson(m);
    }这种方式可以请求少修改配置文件,但还是需要重启服务器的,因为我试着修改密码为错误密码,应用还是能正常访问的。
代码链接:
https://download.csdn.net/download/jiudihanbing/87537958
没积分也可以直接粘贴文章中代码,有积分可以下载代码直接使用,测试代码因为很简单就没有了
上面的方法都不能让运维人员省时省力的解决问题,只是稍微合规和减小点工作量。
我公司有特权账号的产品可以让Tomact context.xml配置文件通过接口获取最新DB密码,同时也不需要重启tomcat,我以后有机会在了解一下实现机制。
![SpringCloud系列(十五)[分布式搜索引擎篇] - 结合实际应用场景学习并使用 RestClient 客户端 API](https://img-blog.csdnimg.cn/a31e8e2640154b7aaef37510a95b2d3f.png#pic_center)


















