切面编程-自定义注解-统一异常拦截-反射
很多有安全要求,或是系统对接的接口都需要进行签名验签。一般我们都是写个工具类,哪个接口需要自己调,把签名字段组织好扔进去。
闲着蛋疼,写了个接口层的验签注解,包装了json返回格式,可以自定义。
1.先定义两个注解
//标识签名接口方法
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Signature {
//入参中一个来源标识,绑定了对应的秘钥盐值登配置
String configKey() default "source";
//入参中的签名字段名,可自定义
String signKey() default "sign";
//签名算法
String algorithm() default "MD5";
}
//标识签名字段
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignField {
}
2.切面及通知定义
@Aspect
@Component
@Slf4j
public class SignAspect {
// 签名开关
private Boolean signSwitch;
// 配置 dao
private ConfigMapper configMapper;
public SignAspect(Boolean signSwitch, ConfigMapper configMapper) {
this.signSwitch = signSwitch;
this.configMapper = configMapper;
}
@Pointcut("@annotation(com.***.signature.Signature)")
public void pointCut(){}
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
//签名开关
if (Boolean.FALSE.equals(signSwitch)) return pjp.proceed();
// 取body参数 @SignField
Object[] params = pjp.getArgs();
if (params == null || params.length == 0){
log.error("signAspect doAround params null");
throw new KKXExeception("验签异常,参数为空!");
}
Object param = params[0];
Map<String,Field> fieldMap = getAllFields(param.getClass());
MethodSignature methodSignature = ((MethodSignature)pjp.getSignature());
Method method = methodSignature.getMethod();
Signature signature = method.getAnnotation(Signature.class);
Field signKey = fieldMap.get(signature.signKey());
signKey.setAccessible(true);
String sign = (String) signKey.get(param);
if (StringUtil.isEmpty(sign)) throw new KKXExeception("验签异常,参数sign为空!");
// 查询配置
Field configKey = fieldMap.get(signature.configKey());
configKey.setAccessible(true);
String source = (String) configKey.get(param);
if (StringUtil.isEmpty(source)) throw new KKXExeception("验签异常,配置参数为空!");
String salt = configMapper.querySignKey(source);
if (StringUtil.isEmpty(salt)) throw new KKXExeception("验签异常,未加载到签名配置!");
StringBuilder sb = getSignStr(param,fieldMap);
String actualSign = SignatureUtil.sign(sb,salt);
if (sign.equals(actualSign)){
return pjp.proceed();
}
throw new KKXExeception("98","签名验证失败");
}
private StringBuilder getSignStr(Object param,Map<String,Field> fieldMap) throws Throwable {
StringBuilder sb = new StringBuilder();
int i = 0;
for (Field field : fieldMap.values()) {
SignField annotation = field.getAnnotation(SignField.class);
if (annotation == null) {
continue;
}
String name = field.getName();
field.setAccessible(true);
Object val = field.get(param);
if (i++ > 0) sb.append("&");
sb.append(name).append("=").append(val);
}
return sb;
}
@AfterThrowing(value = "pointCut()", throwing="e")
public void afterThrowing(Throwable e) {
log.error("signAspect afterThrowing ",e);
if(e instanceof KKXExeception) {
throw (KKXExeception)e;
}
throw new KKXExeception("","验签异常");
}
/**
* 获取本类及其父类的属性的方法
* @param clazz 当前类对象
* @return 字段数组
*/
private static Map<String,Field> getAllFields(Class<?> clazz) {
List<Field> fieldList = new ArrayList<>();
while (clazz != null){
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
}
Map<String,Field> fieldMap = new HashMap<>();
for (Field e : fieldList) {
fieldMap.put(e.getName(), e);
}
return fieldMap;
}
}
3.统一异常拦截
...略
4.bean注册
因为需要装配到具体的项目中,需要进行spring装配。
这个有很多方式.
- 引用jar封装成springboot-starter @bean + springboot spi定义自动装配
- 主项目直接定义@componentscan 扫描到引入jar中的@component
- 主项目中@bean 注册(暂采用)
@Bean
public SignAspect signAspect(@Value("${app.signSwitch:true}") Boolean signSwitch, ConfigMapper configMapper){
return new SignAspect(signSwitch,configMapper);
}