AOP切面实现增删改防止重放攻击
前言
认证重放攻击(高风险)
风险级别: 高风险
风险描述: 攻击者发送一个目标主机已经接收的数据包,特别是在认证过程中,用于认证用户身份时;
风险分析: 攻击者可以使用重放攻击方式伪装成用户,冒充用户身份进行一系列操作;
实现思路
由前端在所有crud等接口在header中全局加上签名【sign】,签名由MD5与AES加密而成(可根据实际生产环境自由决定),然后后端在切片中过滤增删改的接口进行签名认证,同一个签名只在redis中留存一段时间,防止重放攻击;其他不需要防重放的接口(如查询)则跳过验证签名。
自定义注解
这里沿用自定义的接口操作写入日志注解【OperLog】
import java.lang.annotation.*;
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface OperLog
{
String operModul() default ""; // 操作模块
String operType() default ""; // 操作类型
String operDesc() default ""; // 操作说明
// 事件级别
String operLevel() default "低";
}
切片实现
首先明确AOP中几个注解的执行顺序
@Around注解方法的前半部分业务逻辑
->@Before注解方法的业务逻辑
->目标方法的业务逻辑
->@Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)。
->@After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
->@AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
->@AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)
切面代码如下
@Aspect
@Component
public class OperLogAspect {
@Autowired
private StringRedisService redisService;
/**
* 设置操作日志切入点 记录操作日志 在注解的位置切入代码
* <功能详细描述>
*
* @see [类、类#方法、类#成员]
*/
@Pointcut("@annotation(com.bw.dsm.config.aop.OperLog)")
public void operLogPoinCut() {
}
/**
* 设置操作异常切入点记录异常日志 扫描所有controller包下操作
* <功能详细描述>
*
* @see [类、类#方法、类#成员]
*/
@Pointcut("execution(* com.bw.dsm.controller..*.*(..))")
public void operExceptionLogPoinCut() {
}
@Around(value = "operLogPoinCut()")
public Object authorityHandler(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取到请求对象
HttpServletRequest request = attributes.getRequest();
Boolean isValid = true;
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
OperLog opLog = method.getAnnotation(OperLog.class);
if (opLog != null) {
String operDesc = opLog.operDesc();
// 从数据库中获取AES加密密钥
String secKey = sysUserMapper.selectParamByName("sec_key", "sec_key");
if ("新增".equals(operDesc) || "修改".equals(operDesc)
|| "删除".equals(operDesc) || "新增或编辑".equals(operDesc)) {
String sign = request.getHeader("sign");
sign = decrypt(sign, secKey);
Object obj = redisService.hmGet("signKey", sign);
if (obj != null) {
isValid = false;
}
redisService.hmSetByTime("signKey", sign, sign, 5000L);
}
}
if(isValid == false){
return returnLimit(request);
}
} catch (Exception e) {
e.printStackTrace();
}
return joinPoint.proceed();
}
public static String decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != Constant.GLOBAL_INT_SIXTEEN) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(sKey.getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
//先用base64解密
byte[] encrypted1 = new Base64().decode(sSrc);
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original,"utf-8");
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
private String returnLimit(HttpServletRequest request) throws Throwable {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getResponse();
JSONObject obj = new JSONObject();
obj.put("errcode", "0209");
obj.put("errmsg", "签名错误");
response.setHeader("content-type", "text/html;charset=UTF-8");
response.getWriter().print(obj);
response.getWriter().flush();
return null;
}
}
注解使用
@PostMapping("/delLog")
@OperLog(operType = Constant.STRING_THREE,operModul = "日志删除" , operDesc = "删除", operLevel = "高")
public RestResult delLog(@RequestBody RequestParam param)
throws Exception {
return demandService.delLog(param.getShId());
}