添加依赖
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
定义注解
作用于方法上,标记参数需要做编解码
package clown.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface EncryptMethod {
}
作用于字段上,标记字段需要做编解码,以及是否需要脱敏展示
package clown.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface EncryptField {
// 解码后是否脱敏
boolean isDesensitized() default false;
// 脱敏开始位置
int desensitizedBeginIndex() default 0;
// 脱敏长度
int desensitizedLength() default 0;
// 脱敏显示字符
char desensitizedDisplayChar() default '*';
}
加密配置
application.properties 中配置jasypt,也可以使用其他加密工具包替代,或手动加密
jasypt.encryptor.password=PtxbCPZWFfhPjtsvhYo7LA==
jasypt.encryptor.algorithm=PBEWithMD5AndDES
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
AOP处理器
package clown.aop;
import clown.annotation.EncryptField;
import clown.domain.CommRes;
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
@Aspect
@Component
public class EncryptMethodHandler {
@Autowired
private StringEncryptor stringEncryptor;
@Pointcut("@annotation(clown.annotation.EncryptMethod)")
public void encryptMethodPointCut() {}
@Around("encryptMethodPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
// 加密
encrypt(args);
// 业务处理
Object result = joinPoint.proceed(args);
// 解密
decrypt(result);
return result;
}
private void encrypt(Object[] args) throws IllegalAccessException {
if (args.length > 0) {
for (Object obj: args) {
handle(obj, EncryptConstant.ENCRYPT);
}
}
}
private void decrypt(Object result) throws IllegalAccessException {
if (result instanceof CommRes) {
// 处理通用应答包装类
handle(((CommRes) result).getData(), EncryptConstant.DECRYPT);
} else {
handle(result, EncryptConstant.DECRYPT);
}
}
public void handle(Object obj, String type) throws IllegalAccessException {
if (obj == null) {
return;
}
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
boolean isEncrypt = field.isAnnotationPresent(EncryptField.class);
if (!isEncrypt) {
continue;
}
field.setAccessible(true);
Object value = field.get(obj);
if (value instanceof String) {
if (EncryptConstant.ENCRYPT.equals(type)) {
field.set(obj, stringEncryptor.encrypt((String)value));
} else if (EncryptConstant.DECRYPT.equals(type)) {
String decrypt = stringEncryptor.decrypt((String)value);
EncryptField encryptFieldAnnotation = field.getAnnotation(EncryptField.class);
if (encryptFieldAnnotation.isDesensitized()) {
int beginIndex = encryptFieldAnnotation.desensitizedBeginIndex();
int length = encryptFieldAnnotation.desensitizedLength();
if (beginIndex + length > decrypt.length()) {
throw new RuntimeException("脱敏配置异常");
}
field.set(obj, StrUtil.replace(decrypt, beginIndex, beginIndex+length, encryptFieldAnnotation.desensitizedDisplayChar()));
} else {
field.set(obj, decrypt);
}
}
}
}
}
interface EncryptConstant {
String ENCRYPT = "encrypt";
String DECRYPT = "decrypt";
}
}
通用应答包装类
package clown.domain;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class CommRes {
private int code;
private String msg;
private Object data;
public static CommRes success() {
return success(null);
}
public static CommRes success(Object data) {
return new CommRes().setCode(0).setMsg("success").setData(data);
}
public static CommRes fail(String msg) {
return new CommRes().setCode(-1).setMsg(msg);
}
public int getCode() {
return code;
}
public CommRes setCode(int code) {
this.code = code;
return this;
}
public String getMsg() {
return msg;
}
public CommRes setMsg(String msg) {
this.msg = msg;
return this;
}
public Object getData() {
return data;
}
public CommRes setData(Object data) {
this.data = data;
return this;
}
public String toJsonString() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(this);
}
}
测试代码
请求参数
package clown.domain;
import clown.annotation.EncryptField;
public class LoginVO {
@EncryptField(isDesensitized = true, desensitizedLength = 2, desensitizedBeginIndex = 2, desensitizedDisplayChar = '*')
private String username;
@EncryptField(isDesensitized = false, desensitizedLength = 2, desensitizedBeginIndex = 5, desensitizedDisplayChar = '$')
private String password;
public String getUsername() {
return username;
}
public LoginVO setUsername(String username) {
this.username = username;
return this;
}
public String getPassword() {
return password;
}
public LoginVO setPassword(String password) {
this.password = password;
return this;
}
}
控制器
package clown.controller;
import clown.annotation.EncryptMethod;
import clown.domain.CommRes;
import clown.domain.LoginVO;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoginController {
// 使用通用应答包装,方法执行完成后,aop会自动进行解码和脱敏处理
@RequestMapping(value = "login")
@EncryptMethod
@ResponseBody
public CommRes login(@RequestBody LoginVO loginVO) {
System.err.println(loginVO.getUsername()); // 打印加密后的字符串
System.err.println(loginVO.getPassword()); // 打印加密后的字符串
return CommRes.success(loginVO);
}
// 不使用通用应答包装,方法执行完成后,aop会自动进行解码和脱敏处理
@RequestMapping(value = "login")
@EncryptMethod
@ResponseBody
public LoginVO login(@RequestBody LoginVO loginVO) {
System.err.println(loginVO.getUsername()); // 打印加密后的字符串
System.err.println(loginVO.getPassword()); // 打印加密后的字符串
return loginVO
}
}