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还原成了明文--->项目中,对应我们从数据库查询用户信息,展示给用户
相关推荐
百***84458 分钟前
SpringBoot(整合MyBatis + MyBatis-Plus + MyBatisX插件使用)
spring boot·tomcat·mybatis
q***71859 分钟前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
大象席地抽烟16 分钟前
使用 Ollama 本地模型与 Spring AI Alibaba
后端
程序员小假19 分钟前
SQL 语句左连接右连接内连接如何使用,区别是什么?
java·后端
小坏讲微服务20 分钟前
Spring Cloud Alibaba Gateway 集成 Redis 限流的完整配置
数据库·redis·分布式·后端·spring cloud·架构·gateway
方圆想当图灵1 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(下)
分布式·后端·github
方圆想当图灵1 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(上)
分布式·后端·github
小羊失眠啦.1 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust
C++chaofan2 小时前
基于session实现短信登录
java·spring boot·redis·mybatis·拦截器·session
Filotimo_2 小时前
SpringBoot3入门
java·spring boot·后端