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

news2024/11/18 7:39:45

 背景:

公司项目之前使用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.coloradmin.cn/o/1155647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

41.排序练习题(王道2023数据结构第8章综合练习)

试题1&#xff08;王道8.3.3节综合练习2&#xff09;&#xff1a; 编写双向冒泡排序算法&#xff0c;在正反两个方向交替扫描。即第一趟把关键字最大的元素放在序列的最后面&#xff0c;第二趟把关键字最小的元素放在序列最前面&#xff0c;如此反复。 首先实现冒泡排序&…

FIFO 位宽转换

从8位转32位 module tb_fifo();reg clk,rst; initial beginclk0;forever #4.545 clk~clk; end initial beginrst1;#9.09 rst0; endreg [31:0] cnts; always (posedge clk or posedge rst) beginif(rst)begincnts < 32d0;endelsebegincnts < cnts 1b1;end endreg […

美国白宫发布总统令:鼓励AI以安全、可靠的方式发展

美国华盛顿时间10月30日&#xff0c;美国白宫官网发布了&#xff0c;关于发展安全、可靠和值得信赖的AI&#xff08;人工智能&#xff09;的拜登总统行政令。 白宫表示&#xff0c;该行政令为AI安全和保障制定了新标准&#xff0c;保护了用户的数据隐私&#xff0c;促进公平和…

英语——歌曲篇——only you

《only you》(只有你)赏析 很多人都听过The Platters(派特斯乐队)演唱的《only you》(只有你)这首歌曲&#xff0c;尤其是看过在周星驰和罗家英在《大话西游》里面演绎的"无厘头"版本后。 不过&#xff0c;又有几人知道&#xff0c;这首歌曲原来是经典浪漫影片《罗马…

C语言_常用数据类型地址的理解

常用基本数据类型&#xff1a; #include <stdio.h> #include <stdlib.h> #include <stdint.h>int main(){printf("基本数据类型:\n");printf("char: %d\n", sizeof(char));printf("int: %d\n", sizeof(int));printf("do…

1.6 基本安全设计准则

思维导图&#xff1a; 1.6 基本安全设计准则笔记 目标&#xff1a;理解和遵循一套广泛认可的安全设计准则&#xff0c;以指导保护机制的开发。 主要准则&#xff1a; 机制的经济性&#xff1a;安全机制应设计得简单、短小&#xff0c;便于测试和验证&#xff0c;减少漏洞和降…

linux系统的环境变量-搞清环境变量到底是什么

环境变量 引例环境变量常见的环境变量echoexportenvunsetset 通过代码获取环境变量使用第三个参数获取使用全局变量enviorn获取环境变量通过系统调用获取环境变量 环境变量具有全局属性main函数前两个参数的作用 引例 在linux系统中&#xff0c;我们使用ls命令&#xff0c;直接…

Python 算法高级篇:深度优先搜索和广度优先搜索的高级应用

Python 算法高级篇&#xff1a;深度优先搜索和广度优先搜索的高级应用 引言 1. 深度优先搜索&#xff08; DFS &#xff09;回顾2. 广度优先搜索&#xff08; BFS &#xff09;回顾3. 拓扑排序4. 连通性检测5. 最短路径问题6. 案例分析&#xff1a;社交网络分析7. 总结 引言 深…

剑指 Offer || 084.全排列||

题目 给定一个可包含重复数字的整数集合 nums &#xff0c;按任意顺序 返回它所有不重复的全排列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,2] 输出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1…

【蓝桥杯选拔赛真题07】C++小球自由落体 青少年组蓝桥杯C++选拔赛真题 STEMA比赛真题解析

目录 C/C++小球自由落体 一、题目要求 1、编程实现 2、输入输出 二、算法分析

PostgreSQL在云端:部署、管理和扩展你的数据库

随着云计算技术的迅猛发展&#xff0c;将数据库迁移到云端已经成为许多企业的首选。而在众多数据库管理系统中&#xff0c;PostgreSQL因其稳定性、灵活性和可扩展性而成为了不少企业的首选之一。 部署PostgreSQL在云端 将PostgreSQL部署在云端是一个相对简单的过程。云服务提供…

IMX6ULL——GPIO

本章目的&#xff1a;使用GPIO点亮一个LED灯 1.LED原理 &#xff08;1&#xff09;LED类型&#xff1a;插脚LED&#xff1b;贴片LED。 &#xff08;2&#xff09;LED点亮电路 法一&#xff1a; 法二&#xff1a; 我们本章使用法二&#xff0c;使用IMX6ULL的GPIO引脚输出高低电…

Java架构师软件架构的演化和维护

目录 1 导学2 软件架构演化和定义3 面向对象软件架构演化4 软件架构演化方式的分类5 软件架构演化原则6 软件架构演化评估方法7 大型网站架构演化8 软件架构维护想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导学 2 软件架构演化和定义 软件架构的演化和维护就是对…

2023-2024-1高级语言程序设计第1次月考

7-1-1 计算摄氏温度 给定一个华氏温度F&#xff0c;本题要求编写程序&#xff0c;计算对应的摄氏温度C。计算公式&#xff1a;C5(F−32)/9。题目保证输入与输出均在整型范围内。 输入格式: 输入在一行中给出一个华氏温度。 输出格式: 在一行中按照格式“Celsius C”输出对…

软考系统架构师案例分析知识点整理

系统规划&#xff1a;包括系统项目的提出预可行性分析&#xff1b;系统方案的制定、评价和改进&#xff1b;新旧系统的分析和比较&#xff1b;现有软件、硬件和数据资源的有效利用&#xff1b; 软件架构设计&#xff1a;XML技术&#xff1b;基于架构的软件开发过程&#xff1b;…

【算法通关村第一关】链表经典问题

1.两个链表第一个公共子节点 1.这是一道经典的链表问题&#xff1a;输入两条链表&#xff0c;找出他们的第一个公共节点。 使用集合的方法&#xff1a; public ListNode findFirstCommonNodeBySet(ListNode headA,ListNode headB){Set<ListNode> set new HashSet<&g…

Linux服务器部署带Cplex的Java项目

Linux版Cplex安装 Cplex安装包 Cplex 22.1.0 Linux安装包 安装步骤 找到安装包的路径 [roothecs-327697 ~]# cd /www/cplex [roothecs-327697 cplex]# ls cplex_studio2210.linux_x86_64.bin使用chmod 777赋予安装包读、写、执行权限&#xff0c;使用./执行安装 [roothec…

数字IC前端学习笔记:数字乘法器的优化设计(基4布斯编码华莱士树乘法器)

相关阅读 数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 使用基2布斯乘法器虽然能减少乘数中0的数量&#xff0c;但最终还是无法减少部分积的数量&#xff0c;因此一种更合理的编码方式产生了——基4布斯编码。它可以将部…

LeetCode刷题---简单组(六)

文章目录 &#x1f352;题目一 69. x 的平方根&#x1f352;解法一&#x1f352;解法二&#x1f352;题目二 70. 爬楼梯&#x1f352;解法一 &#x1f352;题目一 69. x 的平方根 &#x1f352;解法一 class Solution(object):def mySqrt(self, x):""":type x:…

皮肤渲染方法总结

一、皮肤次表面光照 HDRP用的延迟管线&#xff0c;镜面和散射分开进行计算 UE有透射开启和关闭的效果 &#xff08;一&#xff09;镜面反射 BRDF和Kelemen方法 &#xff08;二&#xff09;次表面散射与透射 1.散射&#xff1a;BRDF与BRSSDF&#xff08;从反射点附近的点进行…