Spring Boot实现数据加密脱敏:注解 反射 AOP

智慧探索者 2021-01-21 ⋅ 60 阅读

引言

在实际开发中,我们经常需要处理敏感数据,例如用户的密码、手机号码等,为了防止数据泄露,通常需要对这些数据进行加密或者脱敏处理。本文将介绍如何利用Spring Boot框架实现数据的加密和脱敏,并通过注解、反射和AOP技术来简化代码的编写和维护。

数据加密

实现思路

数据加密是将原始数据按照一定的算法转换成密文,只有经过相应算法的解密操作才能恢复为原始数据。在Spring Boot中,我们可以利用Spring Security提供的加密工具类来实现数据的加密操作。

示例代码

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class DataEncryptUtil {

    public static String encryptData(String data) {
        // 使用BCryptPasswordEncoder加密数据
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder.encode(data);
    }

    public static boolean verifyData(String raw, String encrypted) {
        // 使用BCryptPasswordEncoder校验数据
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        return encoder.matches(raw, encrypted);
    }
}

数据脱敏

实现思路

数据脱敏是将敏感数据按照一定规则进行处理,使得敏感信息无法直接识别,以达到保护数据隐私的目的。在Spring Boot中,我们可以利用注解和AOP技术来实现数据的脱敏操作。

示例代码

1. 定义脱敏注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveInfo {

    SensitiveType type();

    int prefix() default 0;

    int suffix() default 0;
}

2. 实现脱敏逻辑

import org.springframework.util.StringUtils;

public class DataMaskUtil {

    public static String maskData(String data, SensitiveType type, int prefix, int suffix) {
        if (StringUtils.isEmpty(data)) {
            return data;
        }

        switch (type) {
            case NAME:
                return maskName(data, prefix, suffix);
            case PHONE_NUMBER:
                return maskPhoneNumber(data, prefix, suffix);
            // 添加其他敏感信息的脱敏逻辑
            default:
                return data;
        }
    }

    private static String maskName(String name, int prefix, int suffix) {
        if (StringUtils.isEmpty(name)) {
            return name;
        }

        StringBuilder sb = new StringBuilder();
        if (prefix > 0) {
            sb.append(name, 0, prefix);
        }

        sb.append("***");

        if (suffix > 0 && name.length() > suffix) {
            sb.append(name.substring(name.length() - suffix));
        } else {
            sb.append(name.substring(prefix));
        }

        return sb.toString();
    }

    private static String maskPhoneNumber(String phoneNumber, int prefix, int suffix) {
        if (StringUtils.isEmpty(phoneNumber)) {
            return phoneNumber;
        }

        StringBuilder sb = new StringBuilder();
        if (prefix > 0) {
            sb.append(phoneNumber, 0, prefix);
        }

        sb.append("****");

        if (suffix > 0 && phoneNumber.length() > suffix) {
            sb.append(phoneNumber.substring(phoneNumber.length() - suffix));
        } else {
            sb.append(phoneNumber.substring(prefix));
        }

        return sb.toString();
    }
}

3. 实现数据脱敏的切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.FieldSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;

@Aspect
@Component
public class DataMaskAspect {

    @Pointcut("@annotation(com.example.demo.annotation.SensitiveInfo)")
    public void dataMaskPointcut() {
    }

    @Before("dataMaskPointcut()")
    public void before(JoinPoint joinPoint) throws IllegalAccessException {
        for (Object arg : joinPoint.getArgs()) {
            Field[] fields = arg.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(SensitiveInfo.class)) {
                    field.setAccessible(true);
                    maskFieldValue(arg, field);
                }
            }
        }
    }

    private void maskFieldValue(Object obj, Field field) throws IllegalAccessException {
        SensitiveInfo sensitiveInfo = field.getAnnotation(SensitiveInfo.class);
        field.setAccessible(true);

        String fieldValue = (String) field.get(obj);
        String maskedValue = DataMaskUtil.maskData(fieldValue, sensitiveInfo.type(), sensitiveInfo.prefix(), sensitiveInfo.suffix());

        field.set(obj, maskedValue);
    }
}

使用示例

1. 定义实体类

import com.example.demo.annotation.SensitiveInfo;
import com.example.demo.annotation.SensitiveType;

public class User {

    @SensitiveInfo(type = SensitiveType.NAME, prefix = 1, suffix = 0)
    private String name;

    @SensitiveInfo(type = SensitiveType.PHONE_NUMBER, prefix = 3, suffix = 4)
    private String phoneNumber;

    // 省略其他属性和方法
}

2. 测试数据脱敏

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        User user = new User();
        user.setName("张三");
        user.setPhoneNumber("13812345678");

        return user;
    }
}

3. 测试结果

请求接口 /users/1,返回结果如下:

{
    "name": "张*",
    "phoneNumber": "138****5678"
}

结语

通过上述方式,我们可以简化数据加密和脱敏的代码编写和维护工作,提高开发效率。同时,结合注解、反射和AOP技术,可以灵活地对不同类型的数据进行加密和脱敏处理。当然,根据实际需求,我们还可以扩展更多的脱敏规则,以满足不同场景下的数据保护需求。


全部评论: 0

    我有话说: