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();
    }

}

@SignAnot中有两个参数:
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验签就完成了,非常的简单.若有不明白的地方可以私信我…看到了就会认真回复,代码中若有不对的地方也欢迎评论区讨论一下,三人行必有我师,大家一起学习,一起进步…奥利给

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

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
根据提供的引用内容,我们可以看出这是一个Java Web项目,其中包含了自定义注解和拦截器的使用。在Java中,我们可以通过自定义注解和拦截器来实现功能。具体步骤如下: 1.定义定义注解@Sign,用于标记需要的方法或类。 2.定义拦截器,在拦截器中获取请求参数和名,并进行操作。 下面是一个简单的Java示例: ```java // 自定义注解 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Sign { } // 拦截器 @Component public class SignInterceptor implements HandlerInterceptor { @Autowired private SignService signService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 判断是否有@Sign注解 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Sign sign = handlerMethod.getMethodAnnotation(Sign.class); if (sign == null) { sign = handlerMethod.getBeanType().getAnnotation(Sign.class); } if (sign != null) { // 获取请求参数和名 Map<String, String[]> parameterMap = request.getParameterMap(); String signValue = request.getHeader("sign"); // boolean verifyResult = signService.verifySign(parameterMap, signValue); if (!verifyResult) { // 失败,返回错误信息 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":400,\"msg\":\"失败\"}"); return false; } } } return true; } } // 服务 @Service public class SignService { // 密钥 private static final String SIGN_KEY = "123456"; /** * * * @param parameterMap 请求参数 * @param signValue 名 * @return 结果 */ public boolean verifySign(Map<String, String[]> parameterMap, String signValue) { // 将请求参数按照字典序排序并拼接成字符串 StringBuilder sb = new StringBuilder(); parameterMap.keySet().stream().sorted().forEach(key -> { String[] values = parameterMap.get(key); Arrays.sort(values); sb.append(key).append("=").append(values[0]).append("&"); }); sb.append("key=").append(SIGN_KEY); // 计算名 String sign = DigestUtils.md5Hex(sb.toString()); // 名 return StringUtils.equals(sign, signValue); } } // 控制器 @RestController public class TestController { @Autowired private SignService signService; @PostMapping("/test") @Sign public ResponseBody<AuthCodeJson> getAuthCode(@CurrentUser UserInfo userInfo) { System.out.println(userInfo.getId()); System.out.println(ThreadContextHolder.getUserInfo().getUserId()); return this.success(); } } ``` 在上面的示例中,我们定义了一个自定义注解@Sign,用于标记需要的方法或类。然后定义了一个拦截器SignInterceptor,在拦截器中获取请求参数和名,并进行操作。最后,在控制器TestController中使用@Sign注解标记了需要的方法getAuthCode。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鬼彻|Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值