通过AOP实现加解密和验签

目标:通过注解参数的修改,在不改动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版权协议,转载请附上原文出处链接和本声明。