基于Spring Boot 3 + AOP实现的完整登录防护方案代码
基于Spring Boot 3 + AOP实现的完整登录防护方案代码,整合账号IP双维度防护和混合检测策略以下是基于Spring Boot 3 + AOP实现的完整登录防护方案代码,整合账号/IP双维度防护和混合检测策略:
[*]引入必要依赖(pom.xml)
[*]org.springframework.boot spring-boot-starter-aoporg.springframework.boot spring-boot-starter-data-redisorg.springframework.boot spring-boot-starter-data-jpa
[*]登录日志实体类
[*]@Entity @Table(name = "sys_login_log") @EntityListeners(AuditingEntityListener.class) public class LoginLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String ipAddress; private String userAgent; private LocalDateTime loginTime; private Boolean success; private String failureReason; // Getters & Setters }
[*]登录防护服务类
[*]@Service @RequiredArgsConstructor public class LoginSecurityService { private final RedisTemplate redisTemplate; private final LoginLogRepository loginLogRepository; // 参数配置(建议通过@ConfigurationProperties注入) private static final int MAX_ACCOUNT_ATTEMPTS = 5; private static final int MAX_IP_ATTEMPTS = 100; private static final int TIME_WINDOW = 5; // 分钟 private static final int LOCK_TIME = 15; // 分钟 public boolean validateLogin(String username, String ip) { // 账号维度检测 if (isAccountLocked(username)) { recordLoginLog(username, ip, false, "Account locked"); throw new AccountLockedException(); } // IP维度检测 if (isIPRateLimited(ip)) { recordLoginLog(username, ip, false, "IP rate limited"); throw new IPRateLimitedException(); } // 混合维度检测(IP下账号切换检测) if (isSuspiciousSwitch(ip, username)) { triggerMFA(username, ip); return false; } return true; } public void recordFailure(String identifier, LoginType type) { String key = buildKey(identifier, type); int attempts = incrementWithExpire(key, type == LoginType.ACCOUNT ? MAX_ACCOUNT_ATTEMPTS : MAX_IP_ATTEMPTS, TIME_WINDOW); if (attempts >= (type == LoginType.ACCOUNT ? MAX_ACCOUNT_ATTEMPTS : MAX_IP_ATTEMPTS)) { lockResource(identifier, type, LOCK_TIME); } } private String buildKey(String identifier, LoginType type) { return String.format("login:%s:%s", type == LoginType.ACCOUNT ? "account" : "ip", type == LoginType.ACCOUNT ? identifier : IPUtils.normalize(identifier)); } private boolean isAccountLocked(String username) { return isLocked(username, LoginType.ACCOUNT); } private boolean isIPRateLimited(String ip) { return isLocked(ip, LoginType.IP); } private boolean isLocked(String identifier, LoginType type) { String lockKey = buildKey(identifier, type) + ":locked"; return Boolean.TRUE.equals(redisTemplate.hasKey(lockKey)); } private void lockResource(String identifier, LoginType type, int minutes) { String lockKey = buildKey(identifier, type) + ":locked"; redisTemplate.opsForValue().set(lockKey, "1", minutes, TimeUnit.MINUTES); } private int incrementWithExpire(String key, int threshold, int windowMinutes) { Long count = redisTemplate.opsForValue().increment(key); if (count == null) { redisTemplate.expire(key, windowMinutes, TimeUnit.MINUTES); return 0; } return count.intValue(); } // 混合检测逻辑 private boolean isSuspiciousSwitch(String ip, String username) { String switchKey = "login:switch:" + ip; int switchCount = redisTemplate.opsForZSet().zCard(switchKey).intValue(); if (switchCount >= 3) { return true; } redisTemplate.opsForZSet().add(switchKey, username, System.currentTimeMillis()); redisTemplate.expire(switchKey, 5, TimeUnit.MINUTES); return false; } // 异步日志记录 @Async public void recordLoginLog(String username, String ip, boolean success, String reason) { LoginLog log = new LoginLog(); log.setUsername(username); log.setIpAddress(ip); log.setUserAgent(ServletUtils.getUserAgent()); log.setLoginTime(LocalDateTime.now()); log.setSuccess(success); log.setFailureReason(reason); loginLogRepository.save(log); } // 触发多因素认证 private void triggerMFA(String username, String ip) { // 实现短信/邮件验证逻辑 recordLoginLog(username, ip, false, "MFA triggered"); } }
[*]AOP切面实现
[*]@Aspect @Component @RequiredArgsConstructor public class LoginSecurityAspect { private final LoginSecurityService securityService; @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping) && " + "execution(* com.example.controller.AuthController.login(..))") public void loginEndpoint() {} @Around("loginEndpoint()") public Object validateLogin(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); String username = (String) args; String password = (String) args; HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String ip = IPUtils.getClientIp(request); try { // 执行防护验证 securityService.validateLogin(username, ip); // 继续执行登录逻辑 Object result = joinPoint.proceed(); // 登录成功后重置计数器 securityService.resetAttempts(username, LoginType.ACCOUNT); securityService.resetAttempts(ip, LoginType.IP); return result; } catch (AuthenticationException e) { // 记录失败日志 securityService.recordFailure(username, LoginType.ACCOUNT); securityService.recordFailure(ip, LoginType.IP); securityService.recordLoginLog(username, ip, false, e.getMessage()); throw e; } } @AfterThrowing(pointcut = "loginEndpoint()", throwing = "ex") public void handleLoginFailure(Exception ex) { // 统一异常处理(可结合@ControllerAdvice) } }
[*]工具类
[*]public class IPUtils { public static String getClientIp(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) { return ip.split(","); } ip = request.getHeader("Proxy-Client-IP"); return StringUtils.hasText(ip) ? ip : request.getRemoteAddr(); } public static String normalize(String ip) { return ip.contains(":") ? "" : ip; } }
[*]异常处理
[*]@ControllerAdvice public class SecurityExceptionHandler { @ExceptionHandler(AccountLockedException.class) public ResponseEntity handleAccountLocked() { return ResponseEntity.status(423).body("Account temporarily locked"); } @ExceptionHandler(IPRateLimitedException.class) public ResponseEntity handleIPRateLimit() { return ResponseEntity.status(429).body("Too many requests, please try again later"); } }
[*]配置类(Redis和异步配置)
[*]@Configuration @EnableAsync @EnableCaching public class SecurityConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } @Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); return executor; } }
方案特点:
[*]分层防护体系:
[*]账号层:5次失败锁定15分钟
[*]IP层:5分钟内100次失败触发限流
[*]混合层:检测IP下账号切换行为,触发MFA
[*]AOP实现优势:
[*]完全解耦安全逻辑与业务代码
[*]集中管理横切关注点
[*]支持动态扩展验证规则
[*]性能优化:
[*]Redis原子计数器保证并发安全
[*]异步日志写入避免阻塞主流程
[*]本地缓存+Redis双缓冲机制(示例中未完全展示,可自行扩展)
[*]防御增强:
[*]IPv6地址规范化处理
[*]代理穿透式IP获取
[*]滑动窗口计数算法(需自行扩展ZSET实现)
使用说明:
[*]在登录接口方法添加@PostMapping注解
[*]配置Redis连接信息(application.properties):
[*]spring.redis.host=localhost spring.redis.port=6379 spring.data.redis.repositories.enabled=false
[*]配置数据库连接(MySQL示例):
[*]spring.jpa.hibernate.ddl-auto=update spring.datasource.url=jdbc:mysql://localhost:3306/security_db
该方案可防御以下攻击向量:
[*]单账号暴力破解
[*]分布式IP扫描攻击
[*]账号枚举攻击
[*]慢速字典攻击
建议配合WAF和系统防火墙构建纵深防御体系,并根据实际业务流量调整阈值参数。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! 懂技术并乐意极积无私分享的人越来越少。珍惜 这个有用。 东西不错很实用谢谢分享
页:
[1]