通过shiro框架记录用户登录,登出及浏览器关闭日志

 背景:

公司项目之前使用websocket记录用户登录登出日志及浏览器关闭记录用户登出日志,测试发现仍然存在问题,

问题一:当浏览器每次刷新时websocket其实是会断开重新连接的,因此刷新一下就触发记录登出的日志,其实用户并没有真正退出,

问题二:websocket需要配置,如果线上可能要使用wss等相关nginx都需要运维维护,不熟悉的运维还搞不定,

因此领导要求不要用websocket直接使用shiro不用任何配置,下面是改造后的代码逻辑

第一步:添加创建自定义退出过滤器并发布退出事件

public class CustomLogoutFilter extends  LogoutFilter{
    private static final Logger log = LoggerFactory.getLogger(CustomLogoutFilter.class);
       private ApplicationEventPublisher eventPublisher;
       public CustomLogoutFilter(ApplicationEventPublisher eventPublisher) {
            this.eventPublisher = eventPublisher;
        }
    @Override
    public  boolean preHandle(ServletRequest request, ServletResponse response) throws       Exception {
        eventPublisher.publishEvent(new LogoutEvent(this, request, response));
        return super.preHandle(request, response);
    }
 }

第二步:创建退出监听事件

注”使用监听事件主要是想让代码做到分离,由于项目代码结构的原因,结构简单的可以直接在过滤器中记录日志也没有任何问题

public class LogoutEvent extends ApplicationEvent{
    private static final long serialVersionUID = -8347909954763768519L;
    private ServletRequest request;
    private ServletResponse response;

    public LogoutEvent(Object source, ServletRequest request, ServletResponse response) {
        super(source);
        this.request = request;
        this.response = response;
    }

   #set,get 省略 
}
 

 第三步:将过滤器注入到shiro的配置中

这样当用户退出时就会执行自定义退出过滤器中的方法。

@Configuration
public class ShiroCommonConfig{
    static final Logger logger=LoggerFactory.getLogger(ShiroCommonConfig.class);

    @Bean
    ShiroSessionFactory sessionFactory() {
        return new ShiroSessionFactory();
    }

    //过滤器退出路径配置
    @Bean
    CustomLogoutFilter logoutFilter(ApplicationEventPublisher eventPublisher) {
        String adminPath = Global.getConfig(KeyConsts.ADMIN_PATH) == null ? DefaultValueConsts.DEFAULT_ADMIN_PATH : Global.getConfig(KeyConsts.ADMIN_PATH);
        CustomLogoutFilter logoutFilter = new CustomLogoutFilter(eventPublisher);
        //重定向到登录页
        logoutFilter.setRedirectUrl(adminPath + "/login");
        return logoutFilter;
    }
    
}

第四步:创建退出事件监听器记录退出日志

/***
 * @author wxy
 * @ desc修复之前使用aop时AllModulesAspect切入不到退出的方法从而记录不到用户退出日志
 * @date 20230731
 */
@Component
public class LogoutEventListener  implements ApplicationListener<LogoutEvent>{

    @Override
    public void onApplicationEvent(LogoutEvent event) {
        Subject subject=SecurityUtils.getSubject();
        Session loginSession = subject.getSession();
        ServletRequest servletRequest =event.getRequest();
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String username=(String)loginSession.getAttribute("username_");
        String staffName=(String)loginSession.getAttribute("staffName_");
        String userKind=(String)loginSession.getAttribute("systemUser_");
        String userId=(String)loginSession.getAttribute("userid_");
        String path =servletRequest.getServletContext().getContextPath();
        String ip =httpServletRequest.getRemoteAddr();
        String loginType = Constants.LOGOUTMODEL;
        String loginTitle = Constants.LOGINDESIGNERTITLE;
        String sessionId =loginSession.getId().toString();
        Map<String,String> params = new HashMap<String,String>();
        params.put("userId", userId);
        params.put("username", username);
        params.put("staffName", staffName);
        params.put("ip", ip);
        params.put("loginType", loginType);
        params.put("sessionId", sessionId);
        params.put("loginTitle", loginTitle);
        
        if(StringUtils.isNoneEmpty(path) && path.contains(Constants.CONTEXT_DESIGNER_NAME)) {
            //处理存储存数据库的代码逻辑
             CommonLogUtils.saveLog(params);
            //处理存es的代码逻辑
             handlerLogSaveEs(username,staffName,userKind,userId,httpServletRequest);
        }
    }
}


  第五步:创建登录监听事件

public class LoginSuccessEvent extends ApplicationEvent{
    private static final long serialVersionUID = 3055102020280674571L;
    private ServletRequest request;
    private ServletResponse response;

    public LoginSuccessEvent(Object source, ServletRequest request, ServletResponse response) {
        super(source);
        this.request = request;
        this.response = response;
    } 
}
 

  第六步:创建自定义过滤器并登录成功时发布事件

@Component
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
   
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
               ServletRequest request, ServletResponse response) throws Exception {
               eventPublisher.publishEvent(new LoginSuccessEvent(this, request, response));
               issueSuccessRedirect(request, response);
                return false;
     }
}

 第七步:创建登录成功的监听器记录登录日志

@Component
public class LogSuccessEventListener implements ApplicationListener<LoginSuccessEvent>{

    @Override
    public void onApplicationEvent(LoginSuccessEvent event) {
        Subject subject=SecurityUtils.getSubject();
        Session loginSession = subject.getSession();
        ServletRequest servletRequest =event.getRequest();
        String sessionId =loginSession.getId().toString();
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String username =(String)loginSession.getAttribute("username_");
        String userId =(String)loginSession.getAttribute("userid_");
        String staffName_ =(String)loginSession.getAttribute("staffName_");
        String path =servletRequest.getServletContext().getContextPath();
        String ip =httpServletRequest.getRemoteAddr();
        String loginType = Constants.LOGINMODEL;
        String loginTitle = Constants.LOGINDESIGNERTITLE;
        Map<String,String> params = new HashMap<String,String>();
        params.put("userId", userId);
        params.put("username", username);
        params.put("staffName", staffName_);
        params.put("ip", ip);
        params.put("loginType", loginType);
        params.put("sessionId", sessionId);
        params.put("loginTitle", loginTitle);
        //登录成功后在session对象中放入ip及contextPath名称处理无请求时会话过期
        loginSession.setAttribute("ip", ip);
        loginSession.setAttribute("contextPath", path);
        loginSession.setAttribute("successFlag", "true");
        //保存到数据库
        if(StringUtils.isNoneEmpty(path) && path.contains(Constants.CONTEXT_DESIGNER_NAME)) {
             CommonLogUtils.saveLog(params); ##调用自己的登录接口逻辑
        }
    }

}

 第八步:创建会话过期监听记录浏览器关闭时,回话过期记录退出日志

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;

public class SessionExpireClearListener implements SessionListener {
    private Logger Logger = LoggerFactory.getLogger(SessionExpireClearListener.class);

   @SuppressWarnings("unchecked")
    @Override
    public void onStart(Session session) {
        Logger.debug("session创建:id为 {}", session.getId());
    }

    @Override
    public void onStop(Session session) {
        Logger.info("session停止:id为 {}", session.getId());
        removeInvalidUser(session);
    }

    @Override
    public void onExpiration(Session session) {
        Logger.debug("session过期:id为 {}", session.getId());
        saveExpireLog(session); ###记录用户会话过期日志逻辑
        removeInvalidUser(session);
    }

    @SuppressWarnings("unchecked")
    private void removeInvalidUser(Session session) { }
    }

 这里的回话主要和shiro设置的时间有关

注:代码做了简化处理,只提供思路,具体逻辑还是看自己的项目要求

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/155324.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高级工技能等级认定---网络设备安全

目录 一、DHCP 安全配置 二、SSH配置 三、标准ACL的配置 四、配置交换机端口安全 五、三层交换和ACL的配置 一、DHCP 安全配置 配置要求&#xff1a; 1.给交换机配置enable密码. 2.在交换机上创建VLAN 100&#xff0c;将F0/1-3口改为Access口&#xff0c;并加入到VLAN …

nvm安装步骤

注意事项 不要安装任何版本的node.js&#xff0c;有的话卸载干净&#xff01;注意&#xff1a;要卸载干净了&#xff01; 安装步骤&#xff1a; nvm下载 点击exe文件安装 安装目录选择&#xff1a;D:\NVM 下一步创建nodejs文件放在D:\NVM 下&#xff0c;然后一直next到最后 …

Specializing Smaller Language Models towards Multi-Step Reasoning论文精读

0 Abstract 普遍认为&#xff0c;LLM涌现出来的few-shot learning能力是超大参数模型独有的&#xff08;>100B&#xff09;【emergent abilities】&#xff1b;作者认为&#xff0c;小模型&#xff08;<10B&#xff09;可以将这些能力从大模型&#xff08;>100B&…

学习笔记|单样本t检验|P值|两独立样本均数T检验|规范表达|《小白爱上SPSS》课程:SPSS第五讲 | 两独立样本均数T检验,你会了吗?

目录 学习目的软件版本原始文档P值是假设检验的终极者两独立样本均数T检验一、实战案例二、案例解析三、统计策略四、SPSS操作1、正态性检验2、T检验&#xff08;独立样本T检验&#xff09;结果 五、结果解读Tips&#xff1a;补充知识 六、规范报告1、规范表格2、规范文字 注意…

一个非常实用的Python模块-struct模块

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 struct模块提供了用于在字节字符串和Python原生数据类型之间转换函数&#xff0c;比如数字和字符串。 该模块作用是完成Python数值和C语言结构体的Python字符串形…

android中的Package安装、卸载、更新替换流程

android系统在安装&#xff0c;删除&#xff0c;替换&#xff0c;清除数据等与应用相关的动作时&#xff0c;会发出对应的Broadcast&#xff0c;上层的应用通过注册相应的广播事件来做相应的处理。 官方文档中给出了详尽的罗列&#xff1a; ACTION_PACKAGE_ADDED 一个新应用包已…

什么是神经网络,它的原理是啥?(1)

参考&#xff1a;https://www.youtube.com/watch?vmlk0rddP3L4&listPLuhqtP7jdD8CftMk831qdE8BlIteSaNzD 视频1&#xff1a; 简单介绍神经网络的基本概念&#xff0c;以及一个训练好的神经网络是怎么使用的 分类算法中&#xff0c;神经网络在训练过程中会学习输入的 pat…

监控浏览器页面展示性能的工具

B/S架构&#xff0c;用户都是使用浏览器访问后端服务&#xff0c;产品在开发时需要关注用户的体验&#xff0c;不仅包含交互的友好&#xff0c;性能指标也非常重要。对于后端开发常见的性能指标&#xff0c;可能包含&#xff1a;reponse time&#xff0c;吞吐量等。此外&#x…

笔记本电脑的键盘鼠标如何共享控制另外一台电脑

环境&#xff1a; 联想E14 x2 Win10 across 2.0 问题描述&#xff1a; 笔记本电脑的键盘鼠标如何共享控制另外一台电脑 解决方案&#xff1a; 1.下载across软件&#xff0c;2台电脑都按装&#xff0c;一台设为服务端&#xff0c;一台客户端 2.把配对好设备拖到右边左侧…

PyTorch中grid_sample的使用方法

官方文档首先Pytorch中grid_sample函数的接口声明如下&#xff1a; torch.nn.functional.grid_sample(input, grid, modebilinear, padding_modezeros, align_cornersNone)input : 输入tensor&#xff0c; shape为 [N, C, H_in, W_in]grid: 一个field flow&#xff0c; shape为…

【JVM】垃圾回收机制

【JVM】垃圾回收机制 文章目录 【JVM】垃圾回收机制1. 方法区的回收2. 堆的回收2.1 引用计数法2.2 可达性分析算法 3. 对象引用3.1 强引用3.2 软引用3.3 弱引用3.4 虚引用和终结器引用 4. 垃圾回收算法4.1 标记清除算法4.2 复制算法4.3 标记整理算法4.4 分代垃圾回收算法 5. 垃…

BI零售数据分析,告别拖延症,及时掌握一线信息

在日常的零售数据分析中&#xff0c;经常会因为数据量太大&#xff0c;分析指标太多且计算组合多变而导致数据分析报表难产&#xff0c;零售运营决策被迫拖延症。随着BI数据可视化分析技术的发展&#xff0c;智能化、可视化、自助分析的BI数据分析逐渐成熟&#xff0c;形成一套…