出入参加解密
如果希望通过一个注解同时实现方法的入参解密和出参加密,可以在自定义注解和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() {
}