目标:通过注解参数的修改,在不改动AOP的情况下,适配不同的加解密和验签需求,在AOP中对加密密文解密后传递到接口。
在使用过程中,具体实现加解密的方法必须实现定义的接口,需要加密的某个参数实体类需要继承定义的基本实体类。
SpringUtil工具类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(SpringUtil.class);
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
logger.info("ApplicationContext配置成功,applicationContext对象:{}",SpringUtil.applicationContext);
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name,Class<T> clazz) {
return getApplicationContext().getBean(name,clazz);
}
}
密文实体类
@Data
public class BaseVO {
// 加密密文
private String encryptStr;
}
需要解密的实体类,继承密文实体类,也可以不继承实体类,不继承实体类在使用的时候,需要在该实体类中新增一个密文字段,在接口中注明,AOP就会直接从该实体类中获取加密密文。
继承baseVO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusinessVO extends BaseVO{
private String id;
private String name;
}
不继承baseVO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusinessVO2 {
private String id;
private String name;
// 也可以不继承baseVO,自己定义一个参数,用来存加密后的字符串
private String encryptStr;
}
自定义注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Secret {
/**
* 解密的参数类(用来传递加密数据,只有方法参数中有此类或此类的子类才会执行加解密)
* @return
*/
Class value();
/**
* 参数类中,获取已已加密的字符串的方法名
* @return
*/
String getEncryptStr() default "getEncryptStr";
/**
* 返回结果是否需要加密
* @return
*/
boolean encrypt() default false;
/**
* 验签
* @return boolean
*/
boolean verifySign() default false;
/**
* 实际调用的方法,必须是定义好的service方法的子类的子类,在子类中自己实现用什么方法加密解密和验签
*
* @return {@link Class<? extends SecretService >}
*/
Class<? extends SecretService> getSpecificMethod() default DefaultSecretService.class;
定义的基本接口
public interface SecretService {
/**
* 验签
*
* @param headers 头
* @param body 身体
* @return {@link Pair<Boolean, String>}
*/
Pair<Boolean, String> verifySign(Map<String,String> headers,String body);
/**
* 加密
*
* @param httpHeaders http头信息
* @param body 身体
* @return {@link Pair<Boolean, String>}
*/
Pair<Boolean, String> encrypt(Map<String,String> httpHeaders, Object body);
/**
* 解密
*
* @param encryptedStr 已加密的字符串
* @return {@link Pair<Boolean, String>}
*/
Pair<Boolean, String> decrypt(String encryptedStr);
}
具体实现类去实现加解密和验签的方法
@Slf4j
@Service
public class DefaultSecretService implements SecretService {
@Override
public Pair<Boolean, String> verifySign(Map<String,String> headers,String body) {
log.info("调用了默认的验签方法");
return Pair.of(Boolean.TRUE, "调用了默认的验签方法");
}
@Override
public Pair<Boolean, String> encrypt(Map<String,String> httpHeaders, Object body) {
log.info("调用了默认的加密方法");
return Pair.of(Boolean.TRUE, "调用了默认的加密方法");
}
@Override
public Pair<Boolean, String> decrypt(String encryptedStr) {
log.info("调用了默认的解密方法");
return Pair.of(Boolean.TRUE, "调用了默认的解密方法");
}
}
定义切面
@Aspect
@Component
@Slf4j
public class SecretAOPController {
// 定义切点,使用了@Secret注解的类 或 使用了@Secret注解的方法
@Pointcut("@within(com.agger.springbootaopdemo.annotation.Secret) || @annotation(com.agger.springbootaopdemo.annotation.Secret)")
public void pointcut(){}
// 环绕切面
@Around("pointcut()")
public Object around(ProceedingJoinPoint point){
ResultVO result = null;
// 获取被代理方法参数
Object[] args = point.getArgs();
// 获取被代理对象
Object target = point.getTarget();
// 获取通知签名
MethodSignature signature = (MethodSignature )point.getSignature();
try {
// 获取被代理方法
Method pointMethod = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
// 获取被代理方法上面的注解@Secret
Secret secret = pointMethod.getAnnotation(Secret.class);
// 被代理方法上没有,则说明@Secret注解在被代理类上
if(secret==null){
secret = target.getClass().getAnnotation(Secret.class);
}
// 获取注解上声明的加解密类
Class<SecretService> bean = (Class<SecretService>) secret.getSpecificMethod();
// 是否验签
boolean whetherVerifySign = secret.verifySign();
// 是否加密
boolean whetherEncrypt = secret.encrypt();
//通过工具类获取bean
SecretService specificMethod = SpringUtil.getBean(bean);
//获取request的属性
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//断言
assert attributes != null;
// request对象
HttpServletRequest request = attributes.getRequest();
// 获取所有的header
HashMap<String,String> headersMap = new HashMap<>(16);
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
headersMap.put(key, value);
}
//通过流获取body
InputStreamReader inputStreamReader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8);
StringBuilder stringBuilder;
try (BufferedReader br = new BufferedReader(inputStreamReader)) {
String line;
stringBuilder = new StringBuilder();
while ((line = br.readLine()) != null) {
stringBuilder.append(line);
}
}
String body = stringBuilder.toString();
//加密放在验签的前面
if(secret!=null){
// 获取注解上声明的加解密类
Class clazz = secret.value();
// 获取注解上声明的获取加密参数的方法名称
String encryptStrName = secret.getEncryptStr();
//遍历入参
for (int i = 0; i < args.length; i++) {
// 如果有有参数类型跟注解上声明的加密类的类型一致,说明需要解密的字符串就在这个参数中
if(clazz.isInstance(args[i])){
//将args[i]转换为clazz表示的类对象
Object cast = clazz.cast(args[i]);
// 通过反射,执行获取加密数据的方法,获取加密数据
Method method = clazz.getMethod(encryptStrName);
// 执行方法,获取加密数据
String encryptStr = String.valueOf(method.invoke(cast));
// 加密字符串是否为空
if(StringUtils.isNotBlank(encryptStr)){
// 解密
Pair<Boolean, String> decrypt = specificMethod.decrypt(encryptStr);
// 转换vo
args[i] = JSON.parseObject(decrypt.getRight(), (Type) args[i].getClass());
}
}
// 其他类型,比如基本数据类型、包装类型就不使用加密解密了
}
}
//如果需要验签,在解密之后进行验签
if (whetherVerifySign){
Pair<Boolean, String> verifySign = specificMethod.verifySign(headersMap, body);
//如果验签不通过,直接返回
if (!verifySign.getLeft()){
//判断是否需要加密
return whetherEncrypt?specificMethod.encrypt(headersMap,verifySign.getRight()).getRight():verifySign.getRight();
}
}
// 执行请求
result = (ResultVO) point.proceed(args);
//返回
return whetherEncrypt?specificMethod.decrypt(String.valueOf(result)):result;
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
接口加注解
@Secret(value = BaseVO.class,getEncryptStr="getEncryptStr",encrypt = true,verifySign = true,getSpecificMethod = DefaultSecretService.class)
后记:定义加密参数实体类和获取加密密文方法这两个注解参数,是为了在多参数的情况下,能准确找到需要解密的字符串,如果比较简单,也可以通过定义参数的index来获取需要解密的字符串,toString之后再解密,可以取得一样的效果。
版权声明:本文为dembo原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。