✨ Introducing new features. close #3794 【新需求】登录错误次数限制,连续登录失败锁定账号

This commit is contained in:
卢本伟 committed 2022-04-11 13:40:11 +08:00
1 parent 99fe51e6aa
commit f669d1b968
11 files changed
+214 -12

No files matched your search

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.auth.handler;
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
import com.pig4cloud.pigx.common.security.handler.AuthenticationFailureHandler;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
/**
* @author lengleng
* @date 2022-04-11
*
* 登录操作次数锁定功能
*/
@Slf4j
@Component
@AllArgsConstructor
public class PigxAuthenticationFailureLockEventHandler implements AuthenticationFailureHandler {
private final RedisTemplate<String, String> redisTemplate;
private final KeyStrResolver tenantKeyStrResolver;
private final RemoteUserService userService;
/**
* 密码错误超过 5 此自动锁定
* <p>
* @param authenticationException 登录的authentication 对象
* @param authentication 登录的authenticationException 对象
* @param request 请求
* @param response 响应
*/
@Async
@Override
@SneakyThrows
public void handle(AuthenticationException authenticationException, Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
// 只处理账号密码错误异常
if (!(authenticationException instanceof BadCredentialsException)) {
return;
}
String username = authentication.getName();
String key = String.format("%s:%s:%s", CacheConstants.LOGIN_ERROR_TIMES, tenantKeyStrResolver.key(), username);
Long deltaTimes = ParamResolver.getLong("LOGIN_ERROR_TIMES", 5L);
Long times = redisTemplate.opsForValue().increment(key);
// 自动过期时间
Long deltaTime = ParamResolver.getLong("DELTA_TIME", 1L);
redisTemplate.expire(key, deltaTime, TimeUnit.HOURS);
if (deltaTimes <= times) {
userService.lockUser(username, SecurityConstants.FROM_IN);
}
}
}
@@ -44,7 +44,7 @@ import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
@AllArgsConstructor
public class PigxAuthenticationFailureEventHandler implements AuthenticationFailureHandler {
public class PigxAuthenticationFailureLogEventHandler implements AuthenticationFailureHandler {
private final RemoteLogService logService;
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.auth.handler;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
import com.pig4cloud.pigx.common.security.handler.AuthenticationSuccessHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lengleng
* @date 2022-04-11
*
* 登录操作次数锁定清楚功能
*/
@Slf4j
@Component
@AllArgsConstructor
public class PigxAuthenticationSuccessLockEventHandler implements AuthenticationSuccessHandler {
private final RedisTemplate<String, String> redisTemplate;
private final KeyStrResolver tenantKeyStrResolver;
/**
* 处理登录成功方法
* <p>
* 获取到登录的authentication 对象
* @param authentication 登录对象
* @param request 请求
* @param response 返回
*/
@Async
@Override
public void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
String username = authentication.getName();
String key = String.format("%s:%s:%s", CacheConstants.LOGIN_ERROR_TIMES, tenantKeyStrResolver.key(), username);
redisTemplate.delete(key);
}
}
@@ -36,12 +36,14 @@ import javax.servlet.http.HttpServletResponse;
/**
* @author lengleng
* @date 2018/10/8
* @date 2022-04-11
*
* 登录成功日志记录
*/
@Slf4j
@Component
@AllArgsConstructor
public class PigxAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler {
public class PigxAuthenticationSuccessLogEventHandler implements AuthenticationSuccessHandler {
private final RemoteLogService logService;
@@ -95,4 +95,9 @@ public interface CacheConstants {
*/
String CLIENT_FLAG = "client_config_flag";
/**
* 登录错误次数
*/
String LOGIN_ERROR_TIMES = "login_error_times";
}
@@ -29,6 +29,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author lengleng
@@ -37,7 +38,7 @@ import javax.servlet.http.HttpServletResponse;
public class AuthenticationFailureEventListener implements ApplicationListener<AbstractAuthenticationFailureEvent> {
@Autowired(required = false)
private AuthenticationFailureHandler failureHandler;
private List<AuthenticationFailureHandler> failureHandlerList;
/**
* Handle an application event.
@@ -58,10 +59,9 @@ public class AuthenticationFailureEventListener implements ApplicationListener<A
AuthenticationException authenticationException = event.getException();
Authentication authentication = (Authentication) event.getSource();
// 调用自定义业务实现
if (failureHandler != null) {
failureHandler.handle(authenticationException, authentication, request, response);
}
// 调用自定义业务链实现
failureHandlerList.forEach(
failureHandler -> failureHandler.handle(authenticationException, authentication, request, response));
}
}
@@ -29,6 +29,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author lengleng
@@ -37,7 +38,7 @@ import javax.servlet.http.HttpServletResponse;
public class AuthenticationSuccessEventListener implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired(required = false)
private AuthenticationSuccessHandler successHandler;
private List<AuthenticationSuccessHandler> successHandlerList;
/**
* Handle an application event.
@@ -46,13 +47,12 @@ public class AuthenticationSuccessEventListener implements ApplicationListener<A
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
Authentication authentication = (Authentication) event.getSource();
if (successHandler != null && isUserAuthentication(authentication)) {
if (isUserAuthentication(authentication)) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
successHandler.handle(authentication, request, response);
successHandlerList.forEach(successHandler -> successHandler.handle(authentication, request, response));
}
}
@@ -27,6 +27,7 @@ import com.pig4cloud.pigx.common.core.util.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.List;
@@ -64,4 +65,13 @@ public interface RemoteUserService {
@GetMapping("/user/ancestor/{username}")
R<List<SysUser>> ancestorUsers(@PathVariable("username") String username);
/**
* 锁定用户
* @param username 用户名
* @param from 调用标识
* @return
*/
@PutMapping("/user/lock/{username}")
R<Boolean> lockUser(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM) String from);
}
@@ -201,4 +201,15 @@ public class SysUserController {
return userService.importUser(excelVOList, bindingResult);
}
/**
* 锁定指定用户
* @param username 用户名
* @return R
*/
@Inner
@PutMapping("/lock/{username}")
public R lockUser(@PathVariable String username) {
return userService.lockUser(username);
}
}
@@ -117,4 +117,11 @@ public interface SysUserService extends IService<SysUser> {
*/
R<Boolean> registerUser(UserDTO userDto);
/**
* 锁定用户
* @param username
* @return
*/
R<Boolean> lockUser(String username);
}
@@ -370,4 +370,18 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
return R.ok(saveUser(userDto));
}
/**
* 锁定用户
* @param username 用户名
* @return
*/
@Override
@CacheEvict(value = CacheConstants.USER_DETAILS, key = "#username")
public R<Boolean> lockUser(String username) {
SysUser sysUser = baseMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
sysUser.setLockFlag(CommonConstants.STATUS_LOCK);
baseMapper.updateById(sysUser);
return R.ok();
}
}