IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 利用Spring扩展点对敏感信息加密解密,一文集齐n多知识点「扩展点实战系列」」- 第447篇 -> 正文阅读

[Java知识库]利用Spring扩展点对敏感信息加密解密,一文集齐n多知识点「扩展点实战系列」」- 第447篇

历史文章(文章累计440+)

国内最全的Spring Boot系列之一

国内最全的Spring Boot系列之二

国内最全的Spring Boot系列之三

国内最全的Spring Boot系列之四

国内最全的Spring Boot系列之五》

配置类信息赋值为Java静态变量「扩展点实战系列》」- 第441篇

3种方案扩展RestTemplate让其具备负载均衡(超级详细)「扩展点实战系列」- 第442篇

一个注解@LoadBalanced就能让RestTemplate拥有负载均衡的能力?「扩展点实战系列」- 第443篇

Spring注解@Qualifier的详细用法你知道几种「扩展点实战系列」- 第444篇

利用Spring扩展点模拟Feign实现远程调用(干货满满)「扩展点实战系列」- 第445篇

深入Feign源码吃透Spring扩展点「扩展点实战系列」- 第446篇

悟纤:师傅,你在前面讲到了配置文件加密的方式有2种?

师傅:说2种也可以,说1种也可以,本质的话都是Spring的扩展点来实现的。

师傅:或许,我们换一种方式来表达会更好,配置文件敏感信息的保护是自己使用Spring扩展点实现,还是使用开源的第三方框架来实现。

悟纤:那我们不使用第三方框架,自己实现呢?应该怎么做呢?

师傅:徒儿,你这小马达开启了,就霹雳哗啦的问一堆问题了,但这个习惯不错,值得表扬,时刻保持好奇心是成长的关键。

悟纤:难得能够得到师傅的表扬,让我高兴万分。

师傅:今天为师这一文,会集中n多的以前的知识点,你好好听讲,将会受益匪浅。

悟纤:骑上我心爱的小摩托,带上我的小板凳…?学啊学,学啊学,学到天荒地老,学到海枯石烂。

导读

在前面的一篇文章《SpringBoot配置文件内容加密,实现敏感信息保护》谈到,配置文件内容敏感信息保护有2种常见的方案,我们讲到了其中的一种,这一节来使用另外一种方案,也就是使用Spring的扩展点进行敏感信息的保护。

👇🏻👇🏻👇🏻扩展点实战系列

01.《观察者模式实际应用场景「扩展点实战系列」》

02.《服务信息上报+记录请求信息+监听项目运行状态还能这么玩🐴「扩展点实战系列」》

03.《配置类信息赋值为Java静态变量「扩展点实战系列》」》

04.?《3种方案扩展RestTemplate让其具备负载均衡(超级详细)「扩展点实战系列」》

05.《一个注解@LoadBalanced就能让RestTemplate拥有负载均衡的能力?「扩展点实战系列」》

06.《Spring注解@Qualifier的详细用法你知道几种「扩展点实战系列」》

07.《利用Spring扩展点模拟FeignClient实现远程调用(干货满满)「扩展点实战系列」》

08.《深入FeignClient源码吃透Spring扩展点「扩展点实战系列」》

09.《SpringBoot配置文件内容加密,实现敏感信息保护「扩展点实战系列」》

10.《利用Spring扩展点对敏感信息加密解密,一文集齐n多知识点「扩展点实战系列」》

11.「待拟定」《利用Spring扩展点模拟MyBatis的注解编程「扩展点实战系列」》

一、思路以及编码分析

在开始具体的编码之前,咱们先来简单的分析一下思路以及编码要考虑的一些点。

1.1思路分析

我们来分析一下,对于配置文件内容要加密,要解决的几个问题

(1)其一就是内容加密以及解密,所以需要有一个加密算法,加密算法又分对称加密和非对称加密。

(2)其二就是如何区分配置文件里的内容哪些加密了,哪些未加密。对于这一点的话,可以使用特殊的标识符标记,比如使用[?加密数据?]对加密信息进行包裹

(3)其三就是在哪一个扩展点进行解密。这里主要使用到的扩展点是BeanFactoryPostProcessor,BeanFactory的后处理器。

更多关于加密算法知识,可以关注公众号「SpringBoot」,回复关键词「256?或?md5」,查看文章:

《Spring Boot+Spring Security:?MD5是加密算法吗?》

主要讲解了:加密算法的加密过程、解密过程、加密算法、对称加密、非对称加密。

1.2编码分析

接下来分析一下如何编码:

(1)加密算法准备工作,需要编写一个加密解密算法,这里使用AES对称加密算法。

(2)配置文件内容准备两个参数,一个是不加密的,一个是加密的数据。

(3)配置信息对应的Java类,一般的我们的配置信息都会存放到java类中,方便调用。

(4)使用Spring的扩展点BeanFactoryPostProcessor以及环境变量EnvironmentAware等获取到配置文件的配置内容的信息对信息解密。

二、敏感信息加密解密实战

接下来就是见证奇迹的时刻了,千万不要眨眼睛。

2.1?创建项目

使用idea创建一个项目,取名为spring-boot-decrypt,引入依赖:spring-boot-starter-web。

2.2加密算法工具类

编写一个加密算法工具类,这里使用AES对称加密算法进行加密解密:

package com.kfit.util;import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder;import javax.crypto.Cipher;import javax.crypto.KeyGenerator;import java.security.Key;import java.security.SecureRandom;?public class DecryptUtil {?    private static final String CHARSET = "utf-8";    private static final String ALGORITHM = "AES";    private static final String RANDOM_ALGORITHM = "SHA1PRNG";?    public static String aesEncrypt(String content, String key) {?        if (content == null || key == null) {            return null;        }        Key secretKey = getKey(key);        try {            Cipher cipher = Cipher.getInstance(ALGORITHM);            cipher.init(Cipher.ENCRYPT_MODE, secretKey);            byte[] p = content.getBytes(CHARSET);            byte[] result = cipher.doFinal(p);            BASE64Encoder encoder = new BASE64Encoder();            String encoded = encoder.encode(result);            return encoded;        } catch (Exception e) {            throw new RuntimeException(e);        }    }?    public static String aesDecrypt(String content, String key) {        Key secretKey = getKey(key);        try {            Cipher cipher = Cipher.getInstance(ALGORITHM);            cipher.init(Cipher.DECRYPT_MODE, secretKey);            BASE64Decoder decoder = new BASE64Decoder();            byte[] c = decoder.decodeBuffer(content);            byte[] result = cipher.doFinal(c);            String plainText = new String(result, CHARSET);            return plainText;        } catch (Exception e) {            throw new RuntimeException(e);        }    }?    private static Key getKey(String key) {        if (key == null) {          throw  new NullPointerException("key不能为null");        }?        try {            SecureRandom secureRandom = SecureRandom.getInstance(RANDOM_ALGORITHM);            secureRandom.setSeed(key.getBytes());            KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);            generator.init(secureRandom);            return generator.generateKey();        } catch (Exception e) {            throw new RuntimeException(e);        }    }?    public static void main(String[] args) {        //对于密码 123456加密        String encryptPassword = DecryptUtil.aesEncrypt("123456", "wuqian@springboot");        //结果:NH6/q8SNo929e+bQ4g8dCA==        System.out.println(encryptPassword);        //对于newUserName进行解密        String originPassword = DecryptUtil.aesDecrypt(encryptPassword, "wuqian@springboot");        //结果:123456        System.out.println(originPassword);    }}?

说明:在这个类里有一个main方法,使用key为quqian@springboot对123456进行加密,结果为:NH6/q8SNo929e+bQ4g8dCA==。

2.3?配置文件内容

有了上面的加密信息,那配置文件就水到渠成了。在application.properties添加配置内容:

datasouce.username = rootdatasouce.password?=?Enc[NH6/q8SNo929e+bQ4g8dCA==]

说明:这里对于加密的配置属性,使用Enc[作为前缀,使用]作为后缀,将加密信息进行包裹。

2.4?配置内容对应的java类

配置内容对应的java类:

package com.kfit.config;?import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;?/** * * * @author 悟纤「公众号SpringBoot」 * @date 2022-09-15 * @slogan 大道至简 悟在天成 */@Componentpublic class MyDatasouce {    @Value("${datasouce.username}")    private String username;    @Value("${datasouce.password}")    private String password;?    public String getUsername() {        return username;    }?    public void setUsername(String username) {        this.username = username;    }?    public String getPassword() {        return password;    }?    public void setPassword(String password) {        this.password = password;    }?    @Override    public String toString() {        return "MyDatasouce{" +                "username='" + username + '\'' +                ", password='" + password + '\'' +                '}';    }}

2.5?对配置信息进行解密

接下来就是本文的重点了,也就是对加密信息进行解密,利用Spring的扩展点BeanFactoryPostProcessor(BeanFactory的后处理器)以及EnvironmentAware(用于获取Enviroment的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。),具体的示例代码如下:

package com.kfit.config;?import com.kfit.util.DecryptUtil;import org.springframework.beans.BeansException;import org.springframework.beans.factory.config.BeanFactoryPostProcessor;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;import org.springframework.boot.env.OriginTrackedMapPropertySource;import org.springframework.context.EnvironmentAware;import org.springframework.core.Ordered;import org.springframework.core.env.*;import org.springframework.stereotype.Component;?import java.util.HashMap;import java.util.Map;import java.util.stream.Collectors;import java.util.stream.StreamSupport;?/** * 对于加密的进行解密 * * @author 悟纤「公众号SpringBoot」 * @date 2022-09-15 * @slogan 大道至简 悟在天成 */@Componentpublic class DecryptConfig implements EnvironmentAware, BeanFactoryPostProcessor, Ordered {?    private ConfigurableEnvironment environment;?    private String decryptPrefix = "Enc[";                      // 解密前缀标志 默认值    private String decryptSuffix = "]";                         // 解密后缀标志 默认值    private String decryptKey = "wuqian@springboot";               // 解密秘钥 默认值??    @Override    public void setEnvironment(Environment environment) {        this.environment = (ConfigurableEnvironment)environment;    }?    @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        System.out.println("敏感信息解密开始.....");?        MutablePropertySources propSources = environment.getPropertySources();?        StreamSupport.stream(propSources.spliterator(), false)                .filter(ps -> ps instanceof OriginTrackedMapPropertySource)                .collect(Collectors.toList())                .forEach(ps -> convertPropertySource(propSources,(PropertySource<Map>) ps));?        System.out.println("敏感信息解密结束.....");    }?    @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }?    /**     * 解密相关属性     * @param ps : org.springframework.boot.env.OriginTrackedMapPropertySource     */    private void convertPropertySource(MutablePropertySources propSources,PropertySource<Map> ps) {        //java.util.Collections$UnmodifiableMap        Map source = ps.getSource();        setDecryptProperties(source);?        Map<Object, Object> newMap = new HashMap<>();        source.forEach((k,v) -> {            String value = String.valueOf(v);            if (!value.startsWith(decryptPrefix) || !value.endsWith(decryptSuffix)) {                newMap.put(k,value);                return;            }            String cipherText = value.replace(decryptPrefix, "").replace(decryptSuffix, "");            String clearText = DecryptUtil.aesDecrypt(cipherText, decryptKey);            //System.out.println("DecryptConfig.convertPropertySource-->" + k +"--"+ clearText);            // 不能这么写了,因为UnmodifiableMap不可进行put/remove操作,否则会抛出异常UnsupportedOperationException            //source.put(k,clearText);            newMap.put(k,clearText);        });       propSources.replace(ps.getName(),new OriginTrackedMapPropertySource(ps.getName(),newMap));    }??    /**     *  设置解密属性     * @param source     */    private void setDecryptProperties(Map source) {        decryptPrefix = source.get("decrypt.prefix") == null ? decryptPrefix : String.valueOf(source.get("decrypt.prefix"));        decryptSuffix = source.get("decrypt.suffix") == null ? decryptSuffix : String.valueOf(source.get("decrypt.suffix"));        decryptKey = source.get("decrypt.key") == null ? decryptKey : String.valueOf(source.get("decrypt.key"));    }?    @Override    public int getOrder() {        return Ordered.LOWEST_PRECEDENCE - 100;    }}?

说明:

(1)使用environment获取到所有的属性文件信息。

(2)使用java的lambda表达式进行刷选出满足条件的。

(3)对满足条件的属性文件进行替换处理。

这里要注意,早期是支持直接source.put(k,clearText)替换的,现在的版本不支持的,底层使用了Collections$UnmodifiableMap,因为UnmodifiableMap不可进行put/remove操作,否则会抛出异常UnsupportedOperationException。

👉🏻更多关于EnvironmentAware知识,可以关注公众号「SpringBoot」,回复关键字「433」,查看文章:

《ApplicationContextAwareProcessor普通类获取Spring Bean》

👉🏻更多关于BeanFactoryPostProcessor知识,可以关注公众号「SpringBoot」,回复关键字「428」,查看文章:

《SpringBoot/Spring扩展点系列之叱咤风云BeanFactoryPostProcessor -?第428篇》

👉🏻更多关于Lambda知识,可以关注公众号「SpringBoot」,

回复关键字「lambda」,查看Lambda系列文章:

《Java8新特性:Lambda表达式:小试牛刀》

《Java8新特性:Lambda表达式:过关斩将:使用场景》

《Java8新特性:Lambda表达式: 摸摸里面》

回复关键字「lambda实战」,查看Lambda实战系列文章:

《你真的学会了Lambda表达式了吗?一篇让你学废了不香么?-?第417篇》

《当你的Stream遇上Lambda就爱上了,超级无敌酷酷!-?第418篇》

《java8+lambda+Stream api实战案例学彻底透学废?-?第419篇》

2.6?编写controller测试

编写一个controller打印下信息测试一下看看能不能得到原内容:

package com.kfit;?import com.kfit.config.MyDatasouce;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;?/** *  * * @author 悟纤「公众号SpringBoot」 * @date 2022-09-15 * @slogan 大道至简 悟在天成 */@RestControllerpublic class DemoController {    @Autowired    private MyDatasouce myDatasouce;?    @RequestMapping("/test")    public MyDatasouce test(){        System.out.println(myDatasouce);        return myDatasouce;    }??}

这时候就可以运行项目,访问如下地址:

http://127.0.0.1:8080/test

可以在控制台查看到信息:

说明我们成功的对加密的数据进行解密了。

三、jasypt框架源码

对于这个框架,我们就不细致展开分析了,直接看下核心的关键源码:

EnableEncryptablePropertiesBeanFactoryPostProcessor:

在这里也是实现了BeanFactoryPostProcessor扩展点,具体的处理逻辑交给了converter:

在这里的话,也是使用java的lambda表达式对数据进行了处理,最后交给了makeEncryptable():

在这里对于属性资源进行处理,返回了一个EncryptablePropertySource。

到这里,其实我们也是自己实现一个框架的,只是对于一个开源的框架要思考的点比较多,比如:可扩展性、代码的健壮性、安全性、性能等等。

👉🏻更多关于jasypt的实战,可以关注公众号「SpringBoot」,回复关键字「431」,查看文章:

《SpringBoot配置文件内容加密,实现敏感信息保护?-?第431篇》

总结

最后来总结一下本节的重点内容:

(1)其一就是内容加密以及解密,加密算法文中使用的是AES。

(2)其二就是使用Enc[]包裹加密信息来区分配置文件里的内容哪些加密了,哪些未加密。

(3)其三使用到的扩展点是BeanFactory的后处理器BeanFactoryPostProcessor。

我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。

à悟空学院:https://t.cn/Rg3fKJD

学院中有Spring?Boot相关的课程!点击「阅读原文」进行查看!

SpringBoot视频:http://t.cn/A6ZagYTi

SpringBoot交流平台:https://t.cn/R3QDhU0

SpringSecurity5.0视频:http://t.cn/A6ZadMBe

ShardingJDBC分库分表:http://t.cn/A6ZarrqS

分布式事务解决方案:http://t.cn/A6ZaBnIr

JVM内存模型调优实战:http://t.cn/A6wWMVqG

Spring入门到精通:https://t.cn/A6bFcDh4

大话设计模式之爱你:https://dwz.cn/wqO0MAy7

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 21:01:38  更:2022-10-22 21:03:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/30 13:18:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码