切面編程-自定義注解-統(tǒng)一異常攔截-反射
很多有安全要求笑旺,或是系統(tǒng)對接的接口都需要進行簽名驗簽文留。一般我們都是寫個工具類好唯,哪個接口需要自己調,把簽名字段組織好扔進去燥翅。
閑著蛋疼骑篙,寫了個接口層的驗簽注解,包裝了json返回格式权旷,可以自定義替蛉。
1.先定義兩個注解
//標識簽名接口方法
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Signature {
//入?yún)⒅幸粋€來源標識,綁定了對應的秘鑰鹽值登配置
String configKey() default "source";
//入?yún)⒅械暮灻侄蚊袈龋勺远x
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參數(shù) @SignField
Object[] params = pjp.getArgs();
if (params == null || params.length == 0){
log.error("signAspect doAround params null");
throw new KKXExeception("驗簽異常,參數(shù)為空!");
}
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("驗簽異常,參數(shù)sign為空躲查!");
// 查詢配置
Field configKey = fieldMap.get(signature.configKey());
configKey.setAccessible(true);
String source = (String) configKey.get(param);
if (StringUtil.isEmpty(source)) throw new KKXExeception("驗簽異常,配置參數(shù)為空!");
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 字段數(shù)組
*/
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.統(tǒng)一異常攔截
...略
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);
}