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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值