springboot框架项目实践应用二(敏感信息脱敏续)

1.引言

继上一篇文章:# springboot框架项目实践应用一(敏感信息脱敏)。简单分享了如何实现数据库连接配置密码加密脱敏。实际项目中,我们有更多需要脱敏的应用需求。比如:

  • 用户注册的时候,需要将用户手机号、邮箱地址脱敏处理后,保存数据库;
  • 当用户获取个人信息的时候,即根据用户Id查询存储在数据库中的数据,最后展示的时候,要将用户手机号、邮箱地址还原

要如何才能实现这样的需求?那么今天这篇文章,我们就从这个需求出发,看看如何实现?

2.需求分析

我们先来整理一下需求

  • 用户注册,提供用户信息:用户名称、用户年龄、用户手机号码、用户邮箱地址等
  • 用户注册成功,将用户信息持久化保存到数据库表中
  • 其中用户手机号码、用户邮箱地址属于敏感信息,需要脱敏处理,即数据库中保存的是加密后的数据
  • 用户查询个人信息,从数据库表中根据用户Id获取个人基本信息,包含:用户名称、用户年龄、用户手机号码、用户邮箱地址
  • 前端展示用户信息,需要将脱敏后的用户手机号码,用户邮箱地址还原

假设用户VO对象如下:

java 复制代码
/**
 * 用户VO
 *
 * @author ThinkPad
 * @version 1.0
 */
@Data
public class UserVO {

    private Long userId;
    private String userName;
    private Integer age;

    private String phone;
    private String email;
}

需求其实非常明确,就是如何将phone、email脱敏处理。有小伙伴可能会说,简单呀!只需要

  • 在注册用户方法中,保存数据库前,将UserVO中的phone、email加密
  • 在查询用户信息方法中,将查询到的UserVO中phone、email解密

这么一来,实现还是比较简单的!能实现。但是在实际项目中,我们需要思考这么些问题,你比如说 业务上,有很多地方都需要用户信息

  • 意味着每个地方都需要有加密、解密相关的代码--->导致代码混乱,不优雅,哪一天要是需求变了,说不需要再对用户信息进行脱敏处理--->到处删代码,难以维护
  • 业务上,除了用户信息,还有其它信息要脱敏处理

等等类似需求,有没有更加优雅的实现方式呢?答案是有的,通过aop将脱敏的代码统一收敛起来,不就可以了吗,对吧

下面我们一起来看具体实现过程。

3.需求实现

3.1.导入依赖

xml 复制代码
<!--aop依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--加解密依赖-->
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>

3.2.编写注解

为了让代码实现起来优雅,方便维护,通过注解标注的实现方式,是无比美好的!我们定义两个注解

  • JasyptMethod注解:标注方法,有该注解的方法说明需要脱敏处理
  • JasyptField注解:标注成员变量,有该注解的成员变量,才需要脱敏处理
注解JasyptMethod
java 复制代码
/**
 * 脱敏方法标注
 *
 * @author ThinkPad
 * @version 1.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface JasyptMethod {
}
注解JasyptField
java 复制代码
/**
 * 脱敏字段标注
 *
 * @author ThinkPad
 * @version 1.0
 */
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface JasyptField {
}
修改UserVO

将UserVO中,成员变量phone、email上增加注解JasyptField

java 复制代码
/**
 * 用户VO
 *
 * @author ThinkPad
 * @version 1.0
 */
@Data
public class UserVO {

    private Long userId;
    private String userName;
    private Integer age;

    /**
     * 需要脱敏处理的成员变量
     */
    @JasyptField
    private String phone;
    @JasyptField
    private String email;
}

编写切面

编写切面JasyptAspect,统一处理脱敏。代码我就不解释了,关键代码地方我加了注释,相信熟悉aop的小伙伴都能看的明白

java 复制代码
/**
 * 脱敏处理切面,根据JasyptMethod、JasyptField注解进行处理
 *
 * @author ThinkPad
 * @version 1.0
 */
@Component
@Aspect
@Slf4j
public class JasyptAspect {

    /**
     * 注入加解密 Encryptor
     */
    @Autowired
    private StringEncryptor stringEncryptor;

    @Pointcut("@annotation(cn.edu.anan.aop.JasyptMethod)")
    public void jasyptPointCut() {
    }

    /**
     * 脱敏处理通知-环绕通知
     * @param pjp
     * @return
     */
    @Around("jasyptPointCut()")
    public Object jasyptAround(ProceedingJoinPoint pjp) {
        // 1.参数加密处理
        encrypt(pjp);

        // 2.返回值解密处理
        Object result = decrypt(pjp);
        // 响应前,还原数据
        log.info("3.响应前,还原数据:{}", result);
        return result;

    }

    /**
     * 封装加密处理
     * @param pjp
     */
    private void encrypt(ProceedingJoinPoint pjp){
        try {
            Object[] objects = pjp.getArgs();
            if (objects.length != 0) {
                for (Object o : objects) {
                    // 简单类型加密
                    if (o instanceof String) {
                        o = stringEncryptor.encrypt(String.valueOf(o));
                    } else {
                        // 引用类型加密
                        if(Objects.nonNull(o)){

                            // 打印输出原参数
                            log.info("1.脱敏前,原请求参数:{}", o);

                            // 脱敏
                            Field[] fields = o.getClass().getDeclaredFields();
                            for(Field f : fields){
                                boolean yes = f.isAnnotationPresent(JasyptField.class);
                                if(yes){
                                    f.setAccessible(true);
                                    String realValue = (String)f.get(o);
                                    String encryptValue = stringEncryptor.encrypt(realValue);
                                    f.set(o,encryptValue);
                                }
                            }
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            log.error("jasypt 加密处理异常,异常消息:{}", e.getMessage());
        }
    }

    /**
     * 封装解密处理
     * @param pjp
     * @return
     */
    private Object decrypt(ProceedingJoinPoint pjp) {
        Object result = null;
        try {
            result = pjp.proceed();
            if (Objects.nonNull(result)) {
                if (result instanceof String) {
                    result = stringEncryptor.decrypt(String.valueOf(result));
                } else {
                    Field[] fields = result.getClass().getDeclaredFields();
                    for(Field f : fields){
                        boolean yes = f.isAnnotationPresent(JasyptField.class);
                        if(yes){
                            f.setAccessible(true);
                            String encryptValue = (String)f.get(result);
                            String realValue = stringEncryptor.decrypt(encryptValue);
                            f.set(result,realValue);
                        }
                    }
                }
            }
        } catch (Throwable e) {
            log.error("jasypt 解密处理异常,异常消息:{}", e.getMessage());
        }
        return result;
    }

}

测试效果

在JasyptController中,增加一个测试方法

java 复制代码
/**
     * 请求参数脱敏处理
     * @param vo
     * @return
     */
    @RequestMapping("encrypt")
    @JasyptMethod
    public UserVO encrypt(UserVO vo){
        log.info("2.脱敏后的数据:{}", vo);
        return vo;
    }

启动应用,访问端点:http://127.0.0.1:8090/jasypt/encrypt?userId=1&userName=yhh&age=18&phone=18888637809&email=yhh@126.com

浏览器响应结果:

从应用控制台,我们看到

  • 脱敏前,phone、email都是用户提供的明文
  • 脱敏后,phone、email是加密后的密文--->项目中,对应我们持久化到数据库的用户信息
  • 响应前,phone、email还原成了明文--->项目中,对应我们从数据库查询用户信息,展示给用户
相关推荐
utmhikari1 小时前
【架构艺术】Go语言微服务monorepo的代码架构设计
后端·微服务·架构·golang·monorepo
蜡笔小新星1 小时前
Flask项目框架
开发语言·前端·经验分享·后端·python·学习·flask
计算机学姐1 小时前
基于Asp.net的驾校管理系统
vue.js·后端·mysql·sqlserver·c#·asp.net·.netcore
欢乐少年19043 小时前
SpringBoot集成Sentry日志收集-3 (Spring Boot集成)
spring boot·后端·sentry
浪九天7 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
uhakadotcom7 小时前
Apache CXF 中的拒绝服务漏洞 CVE-2025-23184 详解
后端·面试·github
uhakadotcom7 小时前
CVE-2025-25012:Kibana 原型污染漏洞解析与防护
后端·面试·github
uhakadotcom7 小时前
揭秘ESP32芯片的隐藏命令:潜在安全风险
后端·面试·github
随风九天7 小时前
Spring Boot + MyBatis + MySQL:快速搭建CRUD应用
spring boot·mysql·mybatis
uhakadotcom8 小时前
Apache Camel 漏洞 CVE-2025-27636 详解与修复
后端·面试·github