代码编织梦想

目录

前言

基本概念

敏感数据

数据加密

数据解密

加密方式

解决思路

实现方案

环境配置

依赖配置

代码实现

加密结果

解密结果

总结


前言

相信大家都有这样一个烦恼,就是经常会接到各种推销、广告的电话和短信,如果你没有在他那里留下过联系方式,他又是如何得到了你的联系方式呢?毫无疑问,是个人信息被泄漏了。个人信息的泄漏有人为不合法谋利的因素,也有系统不合理的安全设计造成泄漏的因素。当然系统设计的角度出发,敏感信息需要加密存储的,数据展示的时候也要进行相应的脱敏处理,但是从一些关于个信息泄漏的新闻报道来看,有好多的网站后台竟然是“裸奔”状态,简直太可怕了。其实敏感数据的处理也不复杂,说到底是安全意识不强。当然,这篇文章和大家分享的重点是加密和解密的方法,不是数据安全的重要性。

基本概念

敏感数据

 敏感数据是指那些泄漏后可能会给社会或个人造成严重危害的数据,以个人隐私信息为例,如手机号码、家庭住址、邮箱、身份证号、银行卡帐号、购物网站的支付密码、登陆密码等等。另外从社会的角度出发,也有很多数据是属于敏感数据,如:居民的生物基因信息等等。

数据加密

 数据加密是指对数据重新编码来保护数据,获取实际数据的唯一办法就是使用密钥解密数据;

数据解密

 数据解密与数据加密是相对的,即使用密钥对加密的数据进行解密的过程;

加密方式

 加密的方式,一般是两种:对称加密和非对称加密;

对称加密只有一个秘钥,加密和解密都是用同一个秘钥,如AES、DES等;

非对称加密有两个秘钥,一个是公钥,一个是私钥。使用公钥对数据进行加密,加密后的数据只有私钥可以解密,一般公钥是公开的,私钥是不公开的;如RSA、DSA等;

解决思路

Springboot项目中,客户端通过接口向服务端读取或写入敏感数据时,常会有这样的业务需求:

1、在客户端向服务器端发起写入请求,服务端需要对写入的敏感数据进行加密后存储;

2、在客户端从服务器端向外读取数据的时候,需要对输出的敏感数据里德解密;

显然这种场景,对于加密的方式的选择,对称加密是最好的选择;那么如何实现对写入请求、读取请求的敏感数据的加密、解密处理呢?解决方案如下:

1、自定义两个切面注解,分别是加密切面注解、解密切面注解,作用于需要加密或解密的敏感数据处理的业务处理类的具体业务处理方法上;

2、自定义两个敏感字段处理注解,分别是加密字段注解、解密字段注解,作用于需要输入或输出的对象的敏感字段上;如果输入对象上标记了加密字段注解,则表示该字段在对内写入数据库的时候,需要加密处理;同理,如果输出对象上标记了解密字段注解,则表示该字段在对外输出的时候,需要进行解密;

3、使用面向切面编程,定义两个切面类,分别是加密切面类和解密切面类,选择Spring AOP的环绕通知来具体实现;加密切面类中,以注解的方式定义切入点,用到的注解就是自定义的加密切面注解;

4、如果新增、编辑等写入类的业务请求处理方法上标记了加密切面注解,那么写入请求在正式被业务处理方法处理前,会命中加密切面类,加密切面类的环绕通知方法被触发,然后根据输入的参数对象中的字段是否标记了自定义的加密字段注解,来决定是否对当前字段进行加密处理;

5、同理,如果是查询等读取类的业务请求处理方法上标记了解密切面注解,那么读取请求被业务处理类处理完之后,会命中解密切面类,解密切面类的环绕通知方法被触发,然后根据返回对象的字段是否标记了解密字段注解,来决定是否对当前字段进行解密处理。

实现方案

环境配置

jdk版本:1.8开发工具:Intellij iDEA 2020.1

springboot:2.3.9.RELEASE

mybatis-spring-boot-starter:2.1.4

依赖配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.3</version>
</dependency>

示例代码工作时序图

代码实现

1、自定义四个注解:@DecryptField(解密字段注解)、@EncryptField(加密字段注解)、@NeedEncrypt(解密切面注解)、@NeedEncrypt(加密切面注解),其中@DecryptField作用于需要解密的字段上;@EncryptField作用于需要加密的字段上;@NeedEncrypt作用于需要对入参数进行加密处理的方法上;@NeedDecrypt作用于需要对返回值进行解密处理的方法上;

//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
}
//加密字段注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
//作用于对返回值进行解密处理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}
作用于需要对入参数进行加密处理的方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}

2、把自定义的加密字段注解、解密字段注解标记在需要加密或者解密的字段上;这里表示在写入人员的手机号码、身份证号码、家庭住址门牌号码时,要进行加密处理;在读取人员的手机号码、身份证号码、家庭住址门牌号码时,要进行解密处理;

@Slf4j
@Data
public class Person  {
 private Integer id;
 private String userName;
 private String loginNo;
 @EncryptField
 @DecryptField
 private String phoneNumber;
 private String sex;
 @DecryptField
 @EncryptField
 private String IDCard;
 private String address;
 @EncryptField
 @DecryptField
 private String houseNumber;
}

3、把@NeedEncrypt和@NeedDecrypt标记在需要对入参数、返回值中的敏感字段进行加密、解密处理的业务处理方法上;

@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
    @Autowired
    private IPersonService personService;
    //添加人员信息
    @PostMapping("/add")
    @NeedEncrypt
    public Person add(@RequestBody Person person, Model model) {
        Person result = this.personService.registe(person);
        log.info("//增加person执行完成");
        return result;
    }
    //人员信息列表查询
    @GetMapping("/list")
    @NeedDecrypt
    public List<Person> getPerson() {
        List<Person> persons = this.personService.getPersonList();
        log.info("//查询person列表执行完成");
        return persons;
    }
    //人员信息详情查询
    @GetMapping("/{id}")
    @NeedDecrypt
    public Person get(@PathVariable Integer id) {
        Person person= this.personService.get(id);
        log.info("//查询person详情执行完成");
        return person;
    }
}

4、自定义加密切面类(EncryptAop)和解密切面类(DecryptAop):用@NeedEncrypt注解定义加密切点,在加密切点的环绕通知方法里执行到具体的业务处理方法之前,判断输入对象的参数字段是否标记了@EncryptField(加密字段注解),如果判断结果为true,则使用java反射对该字段进行加密处理,注意这里引用了hutool的工具包,使用了工具包里的加密和解密方法,这里也可以替换成其他的方式;用@NeedDecrypt注解定义解密切点,在解密切点的环绕通知方法里执行完具体的业务处理方法之后,判断输出对象的参数字段是否标记了@DecryptField(解密字段注解),如果判断结果为true,则使用java反射对该 字段进行解密处理;

@Component
@Aspect
@Slf4j
public class EncryptAop {
    /**
     * 定义加密切入点
     */
    @Pointcut(value = "@annotation(com.fanfu.anno.NeedEncrypt)")
    public void pointcut() {
    }

    /**
     * 命中加密切入点的环绕通知
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("//环绕通知 start");
        //获取命中目标方法的入参数
        Object[] args = proceedingJoinPoint.getArgs();
        if (args.length > 0) {
            for (Object arg : args) {
                //按参数的类型进行判断,如果业务中还有其他的类型,可酌情增加
                if (arg != null) {
                    if (arg instanceof List) {
                        for (Object tmp : ((List) arg)) {
                            //加密处理
                            this.deepProcess(tmp);
                        }
                    } else {
                        this.deepProcess(arg);
                    }
                }
            }
        }
        //对敏感数据加密后执行目标方法
        Object result = proceedingJoinPoint.proceed();
        log.info("//环绕通知 end");
        return result;
    }

    public void deepProcess(Object obj) throws IllegalAccessException {
        if (obj != null) {
            //获取对象的所有字段属性并遍历
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断字段属性上是否标记了@EncryptField注解
                if (declaredField.isAnnotationPresent(EncryptField.class)) {
                    //如果判断结果为真,则取出字段属性值,进行加密、重新赋值
                    declaredField.setAccessible(true);
                    Object valObj = declaredField.get(obj);
                    if (valObj != null) {
                        String value = valObj.toString();
                        //开始敏感字段属性值加密
                        String decrypt = this.encrypt(value);
                        //把加密后的字段属性值重新赋值
                        declaredField.set(obj, decrypt);
                    }
                }
            }
        }
    }

    private String encrypt(String value) {
        //这里特别注意一下,对称加密是根据密钥进行加密和解密的,加密和解密的密钥是相同的,一旦泄漏,就无秘密可言,
        //“fanfu-csdn”就是我自定义的密钥,这里仅作演示使用,实际业务中,这个密钥要以安全的方式存储;
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), "fanfu-csdn".getBytes()).getEncoded();
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
        String encryptValue = aes.encryptBase64(value);
        return encryptValue;
    }

}
@Component
@Aspect
@Slf4j
public class DecryptAop {
    /**
     * 定义需要解密的切入点
     */
    @Pointcut(value = "@annotation(com.fanfu.anno.NeedDecrypt)")
    public void pointcut() {
    }

    /**
     * 命中的切入点时的环绕通知
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("//环绕通知 start");
        //执行目标方法
        Object result = proceedingJoinPoint.proceed();
        //判断目标方法的返回值类型
        if (result instanceof List) {
            for (Object tmp : ((List) result)) {
                //数据脱敏处理逻辑
                this.deepProcess(tmp);
            }
        } else {
            this.deepProcess(result);
        }
        log.info("//环绕通知 end");
        return result;
    }

    public void deepProcess(Object obj) throws IllegalAccessException {
        if (obj != null) {
            //取出输出对象的所有字段属性,并遍历
            Field[] declaredFields = obj.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //判断字段属性上是否标记DecryptField注解
                if (declaredField.isAnnotationPresent(DecryptField.class)) {
                    //如果判断结果为真,则取出字段属性数据进行解密处理
                    declaredField.setAccessible(true);
                    Object valObj = declaredField.get(obj);
                    if (valObj != null) {
                        String value = valObj.toString();
                        //加密数据的解密处理
                        value = this.decrypt(value);
                        DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
                        boolean open = annotation.open();
                        //把解密后的数据重新赋值
                        declaredField.set(obj, value);
                    }
                }
            }
        }
    }

    private String decrypt(String value) {
        //这里特别注意一下,对称加密是根据密钥进行加密和解密的,加密和解密的密钥是相同的,一旦泄漏,就无秘密可言,
        //“fanfu-csdn”就是我自定义的密钥,这里仅作演示使用,实际业务中,这个密钥要以安全的方式存储;
        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), "fanfu-csdn".getBytes()).getEncoded();
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
        String decryptStr = aes.decryptStr(value);
        return decryptStr;
    }
}

加密结果

解密结果

总结

这篇着重和大家分享的内容如下:

1、敏感数据的一些基础概念;

2、敏感数据处理的解决思路;

3、敏感数据处理的具体实现方式;

希望我的分享可以给大家一些启示,在遇到类似问题上可以有更好的处理,原创不易,欢迎点赞评论,如果我的分享对你有用,收藏起来吧。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fox9916/article/details/129834146

springboot项目利用ShardingSphere实现数据库字段加解密-爱代码爱编程

springboot项目利用ShardingSphere实现数据库字段加解密 使用默认的加解密方式 引入jar包<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-sprin

springboot数据库敏感数据加密解密-爱代码爱编程

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/a1053765496/article/details/120484312 先上效果图: 加密前  加密后 开始 import org.apache.commons.lang3

Springboot AOP实现指定敏感字段数据加密 (数据加密篇 二)-爱代码爱编程

前言 最近项目组开始关注一些敏感数据的明文相关的事宜 , 其实这些东西也是都有非常成熟的解决方案。 既然最近着手去解决这些事情,那么也顺便给还未了解的大伙普及一下。 这个系列就暂短的分成三篇 :  第一篇    yml配置文件里敏感数据的加密Springboot yml配置参数数据加密 (数据加密篇 一)_默默不代表沉默-CSDN博客  第

Springboot 使用mysql加密解密函数 (数据加密篇 三)-爱代码爱编程

最近项目组开始关注一些敏感数据的明文相关的事宜 , 其实这些东西也是都有非常成熟的解决方案。 既然最近着手去解决这些事情,那么也顺便给还未了解的大伙普及一下。 这个系列就暂短的分成三篇 :  第一篇     yml配置文件里敏感数据的加密https://blog.csdn.net/qq_35387940/article/details/121371

springboot实现字段加密注解_yujiubo2008的博客-爱代码爱编程

1、创建一个注解类 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) public @interface DataEncrypt { } 2、项目启动时加载自定义

springboot+mysql数据库字段加密_以后养只狗的博客-爱代码爱编程

目录 前言 一、需求 二、如何实现 1.简述 2.自定义注解 3.加密实现类 4.拦截器实现 5.使用 6、新需求 总结 前言 提示:在我们进行开发的过程中,可能会遇到银行账号、用户身份证、手机号等铭感字段,用户会要求将这些数据库中的字段加密,以提高数据的安全性 数

springboot 接口层统一加密解密_肥肥技术宅的博客-爱代码爱编程

1. 介绍 在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用 如果我们想保证数据传输的安全,对接口出参加密,入参解密。 但是不想写重复代码,我们可以提供一个通用starter,提供通用加密解密功能 2. 前置知识 2.1 hutool-crypto加密解密工具 hutool-crypto提供了很多加密解密工具,包

springboot 接口加密解密,新姿势-爱代码爱编程

1. 介绍 在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用 如果我们想保证数据传输的安全,对接口出参加密,入参解密。 但是不想写重复代码,我们可以提供一个通用starter,提供通用加密解密功能 2. 前置知识 2.1 hutool-crypto加密解密工具 hutool-crypto提供了很多加密解密工具,包

spring boot框架都有哪些优点和缺点?-爱代码爱编程

相较于传统的Spring框架,Spring Boot 框架具有以下优点。 1.可快速构建独立的 Spring 应用 Spring Boot是一个依靠大量注解实现自动化配置的全新框架。在构建Spring应用时,我们只需要添加相应的场景依赖,Spring Boot就会根据添加的场景依赖自动进行配置,在无须额外手动添加配置的情况下快速构建出一个独立的Sp

application.properties、application.yml和bootstrap.properties、bootstrap.yml的区别-爱代码爱编程

1.springboot配置文件,分成两大类: application.properties application.yml 或者是 Bootstrap.properties Bootstrap.yml *.yml文件格式写

springboot接口入参出参实现加密解密_spring @value 加密-爱代码爱编程

主要使用自定义注解结合springmvc里的RequestBodyAdvice和ResponseBodyAdvice两个类进行实现。 RequestBodyAdvice允许针对接口请求体被读取之前进行修改,ResponseBodyAdvice允许接口出参在被返回之前进行修改。 新建两个自定义注解类,用来标记接口需要进行加密解密。 impo

websecurityconfigureradapter被弃用spring security基于组件化的配置和使用_没有websecurityconfigureradapter-爱代码爱编程

在Spring Security 5.7及之后的版本中WebSecurityConfigurerAdapter将被启用,安全框架将转向基于组件的安全配置。 spring security官方文档 Spring Secur