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 protected]

浏览器响应结果:

从应用控制台,我们看到

  • 脱敏前,phone、email都是用户提供的明文
  • 脱敏后,phone、email是加密后的密文--->项目中,对应我们持久化到数据库的用户信息
  • 响应前,phone、email还原成了明文--->项目中,对应我们从数据库查询用户信息,展示给用户
相关推荐
源码云商2 小时前
基于Spring Boot + Vue的母婴商城系统( 前后端分离)
java·spring boot·后端
还听珊瑚海吗5 小时前
基于SpringBoot的抽奖系统测试报告
java·spring boot·后端
你怎么知道我是队长8 小时前
Go语言标识符
后端·golang
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧9 小时前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea
曼岛_11 小时前
[Java实战]Spring Boot 静态资源配置(十三)
java·开发语言·spring boot
sco528212 小时前
SpringBoot 自动装配原理 & 自定义一个 starter
java·spring boot·后端
曼岛_12 小时前
[Java实战]Spring Boot 快速配置 HTTPS 并实现 HTTP 自动跳转(八)
java·spring boot·http
海风极客13 小时前
《Go小技巧&易错点100例》第三十三篇
开发语言·后端·golang
养军博客13 小时前
Spring boot 简单开发接口
java·spring boot·后端
码农飞哥15 小时前
互联网大厂Java面试实战:从Spring Boot到微服务的技术问答与解析
java·数据库·spring boot·安全·微服务·面试·电商