认证重放攻击(高风险)
风险级别: 高风险
风险描述: 攻击者发送一个目标主机已经接收的数据包,特别是在认证过程中,用于认证用户身份时;
风险分析: 攻击者可以使用重放攻击方式伪装成用户,冒充用户身份进行一系列操作;
由前端在所有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());
- }