在我们开发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验签就完成了,非常的简单.若有不明白的地方可以私信我...看到了就会认真回复,代码中若有不对的地方也欢迎评论区讨论一下,三人行必有我师,大家一起学习,一起进步...奥利给
敲代码容易,思想不易,转载请标注出处,谢谢诸神...
==江山父老能容我,不使人间造孽钱.==
近期评论