java自定义注解sign验签支持MD5等加密方式的拓展

在我们开发API接口的时候,经常会遇到sign加密,解密等验签的操作,以增强接口的安全性。常用的做法是写个signUtil,在方法内进行sign验签,若验签不过则返回错误信息。

代码过于臃肿且看着很不优雅,作为一个文艺男青年的coder一定得将优雅的气质发扬光大,于是自定义注解sign验签就诞生了,讲了这么多废话,先看用例.

DemoController 示例前端控制器

/**
 * @Description: TODO 示例前端控制器
 */
@RestController
@RequestMapping("/api")
public class DemoController {
    
    /**
     * 示例接口 --自定义注解sign验签
     *
     * @param demoVo API接口入参
     * @return 订单详情
     */
    @PostMapping("/get")
    @SignVerify(type = EncryptEnum.MD5, exclude = {"name"})
    public RestResult<String> get(@RequestBody DemoVo demoVo) {
        return RestResultUtils.success();
    }

}
复制代码

@SignVerify中有两个参数:

type: 标记验签方式,本文中只实现了MD5验签方式,若需要其他加密方式可以进行横向拓展
exclule:标记验签对象中被排除属性值,若用此参数进行注明的参数属性名,则该字段不参与验签

@SignVerify 验签注解


/**
 * @Description: TODO 验签注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignVerify {

    /**
     * 验签类型 默认不需要验签
     */
    EncryptEnum type() default EncryptEnum.NOT;

    /**
     * 需要排除的字段
     * <p>
     * 支持多组字段
     * 例如: {"id","name"}
     * <p>
     * 默认全部字段参与验签
     */
    String[] exclude() default {};
}
复制代码

在spring框架中使用自定义注解 必须使用@Aspect进行标记(这里关于AOP的技术点就不过多赘述了)

SignVerifyAspect 验签校验切面


/**
 * @Description: TODO 验签校验切面
 */
@Aspect
@Component
@Slf4j
public class SignVerifyAspect {

    private static final String SIGN_VERIFY_ASPECT_ANNOTATION =
            "@annotation(com.example.sign.demo.SignVerify)";

    @Pointcut(SIGN_VERIFY_ASPECT_ANNOTATION)
    public void cutService() {
    }


    /**
     * 环绕,在执行方法前校验验签sign是否正确不正确则抛出异常
     *
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("cutService()")
    public Object signVerifyAspect(ProceedingJoinPoint point) throws Throwable {
        //首先校验验签是否正确再执行参数
        handle(point);
        return point.proceed();
    }

    private void handle(ProceedingJoinPoint point) throws Exception {
        //解析方法参数
        Object[] params = point.getArgs();
        if (params.length == 1) {
            Object arg = params[0];
            //获取拦截的方法名
            Method currentMethod = AOPUtils.getCurrentMethod(point);
            //获取操作名称
            SignVerify annotation = currentMethod.getAnnotation(SignVerify.class);
            String[] exclude = annotation.exclude();
            EncryptEnum type = annotation.type();
            FunctionUtil
                    .buildVoidIf(new HashMap<>())
                    .add(EncryptEnum.MD5.getCode(),
                            () -> MD5Util.checkSign(arg, Arrays.asList(exclude)))
                    .doIfEqual(type.getCode());
        } else {
         log.error("请求入参不能多于两个,默认只执行第一个入参对象字段属性验签,请按需封装");
        }
    }
}

复制代码

这里解释一下切面类中的FuntionUtil,这里是我自行封装的if函数,为了消除代码中臃肿的if...else语句,主要是利用接口和MAP做的的封装,也比较简单,不过多赘述,上代码.

定义需要执行的方法接口Function

/**
 * @Author: yangjiahui
 * @Description: TODO 无返回值执行函数
 * @Date: 2020/12/22 4:20 下午
 */
public interface Function {
    /**
     * 无返回值的函数
     */
    void invoke();
}

复制代码

定义函数业务类IfVoidFunction

/**
 * IfVoidFunction description  TODO if函数,用于解决 无返回值的执行逻辑
 *
 * @author yangjiahui
 * 属性参数 map 不能为空
 */
public class IfVoidFunction<K> {

    private Map<K, Function> map;

    public Map<K, Function> getMap() {
        return map;
    }

    public void setMap(Map<K, Function> map) {
        this.map = map;
    }

    public IfVoidFunction() {
    }


    /**
     * 通过map类型来保存对应的条件key和方法
     *
     * @param map a map
     */
    public IfVoidFunction(Map<K, Function> map) {
        this.map = map;
    }

   
    /**
     * 添加条件 无返回值函数
     *
     * @param key      需要验证的条件(key)
     * @param function 要执行的方法
     * @return this.
     */
    @NotNull
    public IfVoidFunction<K> add(K key, Function function) {
        this.map.put(key, function);
        return this;
    }


    /**
     * 函数无返回值
     * 确定key是否存在,如果存在,则执行value中的函数。
     * 若key为对象类型 则需重写 equal方法和hashcode方法
     * key值和map中的key值必须一致
     *
     * @param key the key need to verify
     */

    public void doIfEqual(@NotNull K key) {
        if (ObjectUtil.isNotEmpty(this.map) && this.map.containsKey(key)) {
            map.get(key).invoke();
            this.refresh();
        }
    }

    /**
     * 确定key是否存在,如果存在,则执行value中的函数。若不存在执行默认函数
     * <p>
     * 函数无返回值 增加默认执行函数 若传入条件皆不符合 则执行默认函数
     * <p>
     * 若key为对象类型 则需重写 equal方法和hashcode方法
     * key值和map中的key值必须一致
     *
     * @param key the key need to verify 条件值
     */

    public void doIfEqual(@NotNull K key, @NotNull Function defaultFunction) {
        boolean doesItContain = this.map.containsKey(key);
        if (doesItContain) {
            map.get(key).invoke();
            this.refresh();
        }
        if (!doesItContain) {
            defaultFunction.invoke();
        }
    }
    
    public static <K> IfFunctionBuilder<K> builder() {
        return new IfFunctionBuilder<K>();
    }

    public static final class IfFunctionBuilder<K> {

        private Map<K, Function> map;

        private IfFunctionBuilder() {
        }

        public IfFunctionBuilder<K> buildVoidIfFunction(Map<K, Function> map) {
            this.map = map;
            return this;
        }
        public IfVoidFunction<K> build() {
            IfVoidFunction<K> function = new IfVoidFunction<>();
            function.setMap(map);
            return function;
        }
    }

}

复制代码

这里if函数封装就过多赘述,会单独写一篇博客进行拓展

FunctionUtil 函数工具类

/**
 * @Author: yangjiahui
 * @Description: TODO
 * @Date: 2021/03/18 17:43
 */
public class FunctionUtil {
    /**
     * 创建无返回值if函数
     * @param map Map
     * @see  Map
     * @param <K>
     * @return
     */
    public static <K> IfVoidFunction<K> buildVoidIf(Map<K, Function> map) {
        return IfVoidFunction.<K>builder().buildVoidIfFunction(map).build();
    }
}

复制代码

在切面类中还有个AOPUtil 在这里也给到大家

/**
 * @Author: yangjiahui
 * @Description: TODO
 * @Date: 2021/01/11 9:52 上午
 */
public class AOPUtils {

    public static Method getCurrentMethod(ProceedingJoinPoint point) throws NoSuchMethodException {
        Signature sig = point.getSignature();
        MethodSignature methodSignature = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        methodSignature = (MethodSignature) sig;
        Object target = point.getTarget();
        return target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }
}
复制代码

好了,自此一个自定义注解的Sign验签就完成了,非常的简单.若有不明白的地方可以私信我...看到了就会认真回复,代码中若有不对的地方也欢迎评论区讨论一下,三人行必有我师,大家一起学习,一起进步...奥利给

敲代码容易,思想不易,转载请标注出处,谢谢诸神...

==江山父老能容我,不使人间造孽钱.==