利用切面(Aspect)实现出入参加解密及数据脱敏

出入参加解密

如果希望通过一个注解同时实现方法的入参解密和出参加密,可以在自定义注解和AOP切面中进行综合处理。下面是如何实现这个功能的详细步骤。

1. 添加所需依赖

确保`pom.xml`中已经包含了AOP相关的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 创建加解密工具类

假设我们使用AES进行加解密,可以创建一个工具类来处理这个逻辑:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtil {

    private static final String ALGORITHM = "AES";
    private static final String KEY = "1234567890123456"; // 16 bytes key for AES

    public static String encrypt(String data) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String decrypt(String encryptedData) throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        byte[] decodedBytes = Base64.getDecoder().decode(encryptedData);
        return new String(cipher.doFinal(decodedBytes));
    }
}

3. 创建自定义注解

创建一个注解,用于标记需要进行加解密处理的方法。

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Secure {
}

4. 创建AOP切面处理逻辑

定义一个切面类,处理方法的入参解密和出参加密。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class EncryptionAspect {

    @Around("@annotation(secure)")
    public Object secureEndpoint(ProceedingJoinPoint joinPoint, Secure secure) throws Throwable {
        Object[] args = joinPoint.getArgs();

        // 入参解密
        if (args.length > 0 && args[0] instanceof String) {
            args[0] = AESUtil.decrypt((String) args[0]);
        }

        // 调用实际方法
        Object result = joinPoint.proceed(args);

        // 出参加密
        if (result instanceof String) {
            result = AESUtil.encrypt((String) result);
        }

        return result;
    }
}

 5. 应用自定义注解

在你的Controller中应用这个注解。

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class MyController {

    @PostMapping("/secure")
    @Secure
    public String secureEndpoint(@RequestBody String encryptedData) {
        // 这里接收到的数据已经被解密
        // 返回的数据会被加密
        return "Decrypted Data: " + encryptedData;
    }
}

6. 对指定参数进行加解密

调整自定义注解:

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Secure {
    // xxx为你要解密的字段名称
    String secretName() default "xxx";
}

 调整AOP切面处理逻辑

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class EncryptionAspect {

    @Around("@annotation(secure)")
    public Object secureEndpoint(ProceedingJoinPoint joinPoint, Secure secure) throws Throwable {
        Object result = null;
        try {
            Object[] args = joinPoint.getArgs();
            if (secure != null) {
                Object responseObj = args[0];
                Class clazz = responseObj.getClass();
                Field field = clazz.getDeclaredField(secure.secretName());
                field.setAccessible(true);
                if (field.get(responseObj) != null) {
                    String secretStr = AESUtil.decrypt((String) field.get(responseObj));
                    field.set(responseObj, secretStr);
                }
                result = joinPoint.proceed(args);
                // 出参加密
                if (result instanceof String) {
                    result = AESUtil.encrypt((String) result);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

7. 总结

通过以上步骤,我们使用Spring Boot中的AOP功能和自定义注解,实现了对请求参数的解密和对响应结果的加密,这些功能都由一个注解来完成。这种方法具有良好的扩展性和可维护性,适用于需要保护数据传输安全的场景。

敏感数据脱敏

1. 自定义注解 `@Desensitization`

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationSerialize.class)
public @interface Desensitization {
    DesensitizationTypeEnum type() default DesensitizationTypeEnum.MY_RULE;
    int startInclude() default 0;
    int endExclude() default 0;
}

`@Target(ElementType.FIELD)`:指定该注解可以应用于字段。
`@Retention(RetentionPolicy.RUNTIME)`:指定该注解在运行时可用,通过反射可以获取到。
`@JacksonAnnotationsInside`:这是 Jackson 的一个注解,用于自定义注解,使其拥有 Jackson注解的功能。
`@JsonSerialize(using = DesensitizationSerialize.class)`:指定该注解使用         `DesensitizationSerialize` 类进行序列化。
`type()`:指定脱敏类型,默认为 `DesensitizationTypeEnum.MY_RULE`。
`startInclude()` 和 `endExclude()`:指定脱敏的起始和结束位置,用于自定义规则。

2. 枚举 `DesensitizationTypeEnum`

public enum DesensitizationTypeEnum {
    MY_RULE {
        @Override
        public String desensitize(String str, Integer startInclude, Integer endExclude) {
            // 自定义规则的脱敏逻辑
            return str;
        }
    },
    USER_ID {
        @Override
        public String desensitize(String str, Integer startInclude, Integer endExclude) {
            // 用户ID的脱敏逻辑
            return str;
        }
    },
    MOBILE_PHONE {
        @Override
        public String desensitize(String str, Integer startInclude, Integer endExclude) {
            // 手机号脱敏逻辑:如将 12345678901 -> 123****8901
            if (str == null || str.length() < startInclude || str.length() < endExclude || startInclude >= endExclude) {
                throw new IllegalArgumentException("Invalid startInclude or endExclude for mobile phone desensitization.");
            }
            StringBuilder sb = new StringBuilder(str);
            for (int i = startInclude; i < endExclude; i++) {
                sb.setCharAt(i, '*');
            }
            return sb.toString();
        }
    },
    EMAIL {
        @Override
        public String desensitize(String str, Integer startInclude, Integer endExclude) {
            // 邮箱脱敏逻辑
            int atIdx = str.indexOf("@");
            if (atIdx <= 1) {
                return str; // 如果@前只有一位或更少,不做处理
            }
            return str.charAt(0) + "****" + str.substring(atIdx - 1);
        }
    };

    public abstract String desensitize(String str, Integer startInclude, Integer endExclude);
}

DesensitizationTypeEnum:定义了一些常见的脱敏类型,包括自定义规则(`MY_RULE`)、用户ID(`USER_ID`)、手机号(`MOBILE_PHONE`)和邮箱(`EMAIL`)。
desensitize方法:用于不同的枚举类型实现各自的脱敏逻辑。

3. 序列化类 `DesensitizationSerialize`

public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {
    private DesensitizationTypeEnum type;
    private Integer startInclude;
    private Integer endExclude;

    // Constructor
    public DesensitizationSerialize(DesensitizationTypeEnum type, Integer startInclude, Integer endExclude) {
        this.type = type;
        this.startInclude = startInclude;
        this.endExclude = endExclude;
    }

    @Override
    public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(type.desensitize(str, startInclude, endExclude));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
        Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
        if (desensitization != null) {
            return new DesensitizationSerialize(desensitization.type(), desensitization.startInclude(), desensitization.endExclude());
        }
        return serializerProvider.findNullValueSerializer(beanProperty);
    }
}

`DesensitizationSerialize` 继承自 `JsonSerializer<String>` 并实现了 `ContextualSerializer` 接口。

构造函数:用于初始化脱敏类型和位置参数。
serialize方法:该方法重写了父类的方法,用于将敏感数据脱敏后序列化为 JSON 字符串。调用了 `type.desensitize` 方法进行脱敏处理。
createContextual方法:用于创建上下文序列化器。在序列化时,根据字段的注解信息创建相应的 `DesensitizationSerialize` 实例。

4. 使用示例

为了演示如何使用这个枚举和注解,我们可以定义一个包含手机号字段的实体类:

public class User {

    private String name;

    @Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE, startInclude = 3, endExclude = 7)
    private String phoneNumber;

    // Getters and setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                '}';
    }
}

附:

@Around("secretPointcut()") 与 @Around("@annotation(com.**.Secret)")主要区别在于如何定义和引用切入点
* 1、@Around("@annotation(com.**.Secret)"),如果切入点表达式简单且只使用一次,这种方式更简洁
* 2、@Around("secretPointcut()")方式首先使用@Pointcut注解定义了一个命名的切入点,适用于复杂的或需要多次复用的切入点
/**
     * 说明:@within标注类,@annotation标注方法,@within && @annotation表示匹配添加注解的类中并且添加了注解的方法
     */
    @Pointcut("@within(com.**.Secret) || @annotation(com.**.Secret)")
    public void secretPointcut() {

    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值