• AOP切面实现增删改防止重放攻击


    前言

    认证重放攻击(高风险)

    风险级别: 高风险

    风险描述: 攻击者发送一个目标主机已经接收的数据包,特别是在认证过程中,用于认证用户身份时;

    风险分析: 攻击者可以使用重放攻击方式伪装成用户,冒充用户身份进行一系列操作;


    实现思路

            由前端在所有crud等接口在header中全局加上签名【sign】,签名由MD5与AES加密而成(可根据实际生产环境自由决定),然后后端在切片中过滤增删改的接口进行签名认证,同一个签名只在redis中留存一段时间,防止重放攻击;其他不需要防重放的接口(如查询)则跳过验证签名。


    自定义注解

    这里沿用自定义的接口操作写入日志注解【OperLog】

    1. import java.lang.annotation.*;
    2. @Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
    3. @Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
    4. @Documented
    5. public @interface OperLog
    6. {
    7. String operModul() default ""; // 操作模块
    8. String operType() default ""; // 操作类型
    9. String operDesc() default ""; // 操作说明
    10. // 事件级别
    11. String operLevel() default "低";
    12. }

    切片实现

    首先明确AOP中几个注解的执行顺序

    @Around注解方法的前半部分业务逻辑
    ->@Before注解方法的业务逻辑
    ->目标方法的业务逻辑
    ->@Around注解方法的后半部分业务逻辑(@Around注解方法内的业务逻辑若对ProceedingJoinPoint.proceed()方法没做捕获异常处理,直接向上抛出异常,则不会执行Around注解方法的后半部分业务逻辑;若做了异常捕获处理,则会执行)。
    ->@After(不管目标方法有无异常,都会执行@After注解方法的业务逻辑)
    ->@AfterReturning(若目标方法无异常,执行@AfterReturning注解方法的业务逻辑)
    ->@AfterThrowing(若目标方法有异常,执行@AfterThrowing注解方法的业务逻辑)

    切面代码如下

    1. @Aspect
    2. @Component
    3. public class OperLogAspect {
    4. @Autowired
    5. private StringRedisService redisService;
    6. /**
    7. * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
    8. * <功能详细描述>
    9. *
    10. * @see [类、类#方法、类#成员]
    11. */
    12. @Pointcut("@annotation(com.bw.dsm.config.aop.OperLog)")
    13. public void operLogPoinCut() {
    14. }
    15. /**
    16. * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
    17. * <功能详细描述>
    18. *
    19. * @see [类、类#方法、类#成员]
    20. */
    21. @Pointcut("execution(* com.bw.dsm.controller..*.*(..))")
    22. public void operExceptionLogPoinCut() {
    23. }
    24. @Around(value = "operLogPoinCut()")
    25. public Object authorityHandler(ProceedingJoinPoint joinPoint) throws Throwable {
    26. ServletRequestAttributes attributes =
    27. (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    28. //获取到请求对象
    29. HttpServletRequest request = attributes.getRequest();
    30. Boolean isValid = true;
    31. try {
    32. // 从切面织入点处通过反射机制获取织入点处的方法
    33. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    34. // 获取切入点所在的方法
    35. Method method = signature.getMethod();
    36. OperLog opLog = method.getAnnotation(OperLog.class);
    37. if (opLog != null) {
    38. String operDesc = opLog.operDesc();
    39. // 从数据库中获取AES加密密钥
    40. String secKey = sysUserMapper.selectParamByName("sec_key", "sec_key");
    41. if ("新增".equals(operDesc) || "修改".equals(operDesc)
    42. || "删除".equals(operDesc) || "新增或编辑".equals(operDesc)) {
    43. String sign = request.getHeader("sign");
    44. sign = decrypt(sign, secKey);
    45. Object obj = redisService.hmGet("signKey", sign);
    46. if (obj != null) {
    47. isValid = false;
    48. }
    49. redisService.hmSetByTime("signKey", sign, sign, 5000L);
    50. }
    51. }
    52. if(isValid == false){
    53. return returnLimit(request);
    54. }
    55. } catch (Exception e) {
    56. e.printStackTrace();
    57. }
    58. return joinPoint.proceed();
    59. }
    60. public static String decrypt(String sSrc, String sKey) throws Exception {
    61. try {
    62. // 判断Key是否正确
    63. if (sKey == null) {
    64. System.out.print("Key为空null");
    65. return null;
    66. }
    67. // 判断Key是否为16位
    68. if (sKey.length() != Constant.GLOBAL_INT_SIXTEEN) {
    69. System.out.print("Key长度不是16位");
    70. return null;
    71. }
    72. byte[] raw = sKey.getBytes("utf-8");
    73. SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    74. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    75. IvParameterSpec iv = new IvParameterSpec(sKey.getBytes());
    76. cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
    77. //先用base64解密
    78. byte[] encrypted1 = new Base64().decode(sSrc);
    79. try {
    80. byte[] original = cipher.doFinal(encrypted1);
    81. String originalString = new String(original,"utf-8");
    82. return originalString;
    83. } catch (Exception e) {
    84. System.out.println(e.toString());
    85. return null;
    86. }
    87. } catch (Exception ex) {
    88. System.out.println(ex.toString());
    89. return null;
    90. }
    91. }
    92. private String returnLimit(HttpServletRequest request) throws Throwable {
    93. HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
    94. .getRequestAttributes()).getResponse();
    95. JSONObject obj = new JSONObject();
    96. obj.put("errcode", "0209");
    97. obj.put("errmsg", "签名错误");
    98. response.setHeader("content-type", "text/html;charset=UTF-8");
    99. response.getWriter().print(obj);
    100. response.getWriter().flush();
    101. return null;
    102. }
    103. }

    注解使用

    1. @PostMapping("/delLog")
    2. @OperLog(operType = Constant.STRING_THREE,operModul = "日志删除" , operDesc = "删除", operLevel = "高")
    3. public RestResult delLog(@RequestBody RequestParam param)
    4. throws Exception {
    5. return demandService.delLog(param.getShId());
    6. }

  • 相关阅读:
    Docker搭建Redis cluster集群
    MyQuartz高可用定时任务管理
    validator库的使用详解
    第2讲:Vue开发环境的搭建及运行
    CSS基础——复合选择器
    红日靶场五(vulnstack5)渗透分析
    零代码编程:用ChatGPT将特定文件标题重命名为特定格式
    关于#华为#的问题:二、半导体设备:半导体封装设备、半导体扩散设备、半导体焊接设备、半导体清洗设备、半导体 测试设备、半导体制冷设备、半导体氧化设备等(相关搜索:人工智能)
    Qt中常见的文件操作
    [附源码]Python计算机毕业设计Django学生疫情防控信息填报系统
  • 原文地址:https://blog.csdn.net/qq_37634156/article/details/126617756