保护敏感数据:Spring Boot中敏感字段加密解密的高效解决方案

前言

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

基本概念

敏感数据

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

数据加密

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

数据解密

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

加密方式

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

对称加密只有一个秘钥,加密和解密都是用同一个秘钥,如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 persnotallow= 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、敏感数据处理的具体实现方式;

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/139152.html<

(0)
管理的头像管理
上一篇2025-03-03 08:34
下一篇 2025-03-03 08:35

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注