在构思操作之前,我的想法是使用AOP进行切面操作,但是Security整合了一系列的登录操作和结果,我对其不是很熟,没有时间去深究,就参考了大佬的博客。
Spring Security 中基于ApplicationListener记录用户登录日志(含登录成功和登录失败)_springsecurity登录日志_CHQIUU的博客-CSDN博客
选择使用了监听器,但是获取Request的时候出了问题,因为要获得登录人的IP和浏览器版本等等的信息。
但是实际使用
Objects.requireNonNull(( (ServletRequestAttributes) RequestContextHolder.getRequestAttributes()
以及类似的都获取不到request
后面学到了再用监听器监听request,并存到线程里
我选择是提前获取所需信息,并存到线程里
@Component
public class WebRequestListener implements ServletRequestListener {
public static final ThreadLocal<String> ipHolder = new ThreadLocal<>();
public static final ThreadLocal<String> browserHolder = new ThreadLocal<>();
public static final ThreadLocal<String> osHolder = new ThreadLocal<>();
@Override
public void requestInitialized(ServletRequestEvent event) {
HttpServletRequest request = (HttpServletRequest) event.getServletRequest();
String userAgent = request.getHeader("User-Agent");
String remoteAddr = ServletUtil.getClientIP(request, "");
// 解析agent字符串
UserAgent userAgent1 = UserAgent.parseUserAgentString(userAgent);
// 获取浏览器对象
Browser browser = userAgent1.getBrowser();
// 获取操作系统对象
OperatingSystem operatingSystem = userAgent1.getOperatingSystem();
// System.out.println("浏览器名:" + browser.getName());
// System.out.println("浏览器类型:" + browser.getBrowserType());
// System.out.println("浏览器家族:" + browser.getGroup());
// System.out.println("浏览器生产厂商:" + browser.getManufacturer());
// System.out.println("浏览器使用的渲染引擎:" + browser.getRenderingEngine());
// System.out.println("浏览器版本:" + userAgent1.getBrowserVersion());
// System.out.println("操作系统名:" + operatingSystem.getName());
// System.out.println("访问设备类型:" + operatingSystem.getDeviceType());
// System.out.println("操作系统家族:" + operatingSystem.getGroup());
// System.out.println("操作系统生产厂商:" + operatingSystem.getManufacturer());
ipHolder.set(remoteAddr);
browserHolder.set(browser.getName());
osHolder.set(operatingSystem.getName());
}
}
然后在上述链接中大佬的监听器增加自己需要的属性,并save到数据库中就行了
import com.blb.pro.entity.Logininfor;
import com.blb.pro.service.LogininforService;
import com.blb.pro.util.ListenerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.*;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class AuthenticationFailureListener implements
ApplicationListener<AbstractAuthenticationFailureEvent>{
@Autowired
private LogininforService logininforService;
@Override
public void onApplicationEvent(AbstractAuthenticationFailureEvent event) {
String message;
if (event instanceof AuthenticationFailureBadCredentialsEvent) {
//提供的凭据是错误的,用户名或者密码错误
message = "提供的凭据是错误的,用户名或者密码错误";
} else if (event instanceof AuthenticationFailureCredentialsExpiredEvent) {
//验证通过,但是密码过期
message = "验证通过,但是密码过期";
} else if (event instanceof AuthenticationFailureDisabledEvent) {
//验证过了但是账户被禁用
message = "验证过了但是账户被禁用";
} else if (event instanceof AuthenticationFailureExpiredEvent) {
//验证通过了,但是账号已经过期
message = "验证通过了,但是账号已经过期";
} else if (event instanceof AuthenticationFailureLockedEvent) {
//账户被锁定
message = "账户被锁定";
} else if (event instanceof AuthenticationFailureProviderNotFoundEvent) {
//配置错误,没有合适的AuthenticationProvider来处理登录验证
message = "配置错误";
} else if (event instanceof AuthenticationFailureProxyUntrustedEvent) {
// 代理不受信任,用于Oauth、CAS这类三方验证的情形,多属于配置错误
message = "代理不受信任";
} else if (event instanceof AuthenticationFailureServiceExceptionEvent) {
// 其他任何在AuthenticationManager中内部发生的异常都会被封装成此类
message = "内部发生的异常";
} else {
message = "其他未知错误";
}
// 登录账号
String username = (String) event.getAuthentication().getPrincipal();
//状态码
String status = "1";
//操作时间
LocalDateTime time = LocalDateTime.now();
Logininfor loginLog = ListenerUtils.outAndGetLog(username, message, time, status);
logininforService.save(loginLog);
}
}
这里编写了几个工具类 因为在登录成功和登录失败处 除了获取msg信息以为要做的事情不同
ListenerUtils从线程获取ip,浏览器,系统版本
我们传入 用户名 提示信息 时间 状态码
获得对象 并存入
然后失败监听器也差不多的操作
import com.blb.pro.entity.Logininfor;
import com.blb.pro.service.LogininforService;
import com.blb.pro.util.ListenerUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.annotation.WebListener;
import java.time.LocalDateTime;
/**
* 用户登录成功监听器事件
*
* @author chqiuu
*/
@Slf4j
@Component
@WebListener
public class AuthenticationSuccessListener
implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private LogininforService logininforService;
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
/*info_id 访问ID
*user_name 用户账号 user.
* ipaddr 登录IP地址
* login_location 登录地点 判断ip
* browser 浏览器类型 获取
* os 操作系统 获取
* status 登录状态(0成功 1失败) 0
* msg 提示消息 登录成功
* login_time 访问时间
*/
//用户通过输入用户名和密码登录成功
// 登录账号
User user = (User) event.getAuthentication().getPrincipal();
String username = user.getUsername();
String msg ="登录成功";
String status = "0";
LocalDateTime time = LocalDateTime.now();
Logininfor loginLog = ListenerUtils.outAndGetLog(username, msg, time, status);
logininforService.save(loginLog);
}
}
与此同时我还要监听退出的状态,便模仿前面的监听器写了一下
import com.blb.pro.entity.Logininfor;
import com.blb.pro.service.LogininforService;
import com.blb.pro.util.ListenerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import static com.blb.pro.listener.WebRequestListener.*;
@Component
public class LogoutSuccessListener implements ApplicationListener<LogoutSuccessEvent> {
@Autowired
private LogininforService logininforService;
@Override
public void onApplicationEvent(LogoutSuccessEvent event) {
User user = (User) event.getAuthentication().getPrincipal();
String username = user.getUsername();
String msg ="退出成功";
String status = "0";
LocalDateTime time = LocalDateTime.now();
Logininfor loginLog = ListenerUtils.outAndGetLog(username, msg, time, status);
logininforService.save(loginLog);
}
}
到此,我们就完成了对Security登录结果的操作日志记录
根据业务需求,我们适当调整自己的工具类就好
-- 2023.9.5
应博友的需求,就把工具类贴一下,其实写的不是很好,就是把重复方法收集起来,让代码规范点,如果大家有更好的建议可以指出。
import com.blb.pro.entity.Logininfor;
import java.time.LocalDateTime;
import static com.blb.pro.listener.WebRequestListener.*;
public class ListenerUtils {
static String ip = ipHolder.get();
static String os = osHolder.get();
static String browser = browserHolder.get();
//可以自己定义一个ip工具类来接收ip,自己定义哪种是内网,哪种公网,判断属性之后后面使用
static String ipType = IPUtils.isPublicAndPrivate(ip);
public static void outputLog(String username, String msg, LocalDateTime loginTime, String status) {
System.out.println("监听器 ***************** 监听器");
System.out.println(username);
System.out.println("ip为" + ip);
System.out.println("类型:" + ipType);
System.out.println("浏览器为" + browser);
System.out.println("操作系统为" + os);
System.out.println("访问时间为:" + loginTime);
System.out.println("操作msg为:" + msg);
System.out.println("状态码为:" + status);
}
public static Logininfor getLog(String username, String msg, LocalDateTime loginTime, String status) {
Logininfor loginLog = new Logininfor();
loginLog.setUserName(username);
loginLog.setIpaddr(ip);
loginLog.setLoginLocation(ipType);
loginLog.setBrowser(browser);
loginLog.setOs(os);
loginLog.setStatus(status);
loginLog.setMsg(msg);
loginLog.setLoginTime(loginTime);
return loginLog;
}
public static Logininfor outAndGetLog(String username, String msg, LocalDateTime loginTime, String status) {
outputLog(username, msg, loginTime, status);
return getLog(username, msg, loginTime, status);
}
}