通过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.rhkb.cn/news/176415.html

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

相关文章

C语言KR圣经笔记 2.11条件表达式 2.12优先级和求值顺序

2.11条件表达式 if (a > b) z a; else z b; 上面的语句计算 a 和 b 中的最大值并存入 z。而使用三元操作符 ? : 的条件表达式&#xff0c;为这个结构及类似结构提供了另一种写法。在如下表达式 expr1 ? expr2 : expr3 中&#xff0c;首先对 expr1 求值。如果值非0 …

验证链(CoVe)降低LLM中的幻觉10.31

验证链&#xff08;CoVE&#xff09;降低LLM中的幻觉 摘要1 引言2 相关工作3 验证链&#xff08;Chain-of-Verification&#xff09;3.1 生成基准回答3.2 计划验证3.3 执行验证3.4 最终验证的回答 4 实验&#xff08;直译&#xff09;4.1 任务4.1.1 WIKIDATA4.1.2 WIKI-CATEGOR…

ByteBuffer的原理和使用详解

ByteBuffer是字节缓冲区&#xff0c;主要用户读取和缓存字节数据&#xff0c;多用于网络编程&#xff0c;原生的类&#xff0c;存在不好用&#xff0c;Netty采用自己的ByteBuff&#xff0c;对其进行了改进 1.ByteBuffer的2种创建方式 1.ByteBuffer buf ByteBuffer.allocate(i…

mediapipe 训练自有图像数据分类

参考&#xff1a; https://developers.google.com/mediapipe/solutions/customization/image_classifier https://colab.research.google.com/github/googlesamples/mediapipe/blob/main/examples/customization/image_classifier.ipynb#scrollToplvO-YmcQn5g 安装&#xff1a…

【漏洞复现】酒店宽带运营系统RCE

漏洞描述 安美数字 酒店宽带运营系统 server_ping.php 远程命令执行漏洞 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c;遵守公共秩序&#xff0c;尊重社会公德&#xff0c;不得利用网络从事危害国家安全、荣誉和利益&#xff…

生成瑞利信道(Python and Matlab)

channel h k h_k hk​ is modeled as independent Rayleigh fading with average power loss set as 10^−3 Python import numpy as np# Set the parameters average_power_loss 1e-3 # Average power loss (10^(-3)) num_samples 1000 # Number of fading samples to …

1深度学习李宏毅

目录 机器学习三件事&#xff1a;分类&#xff0c;预测和结构化生成 2、一般会有经常提到什么是标签label&#xff0c;label就是预测值&#xff0c;在机器学习领域的残差就是e和loss​编辑3、一些计算loss的方法&#xff1a;​编辑​编辑 4、可以设置不同的b和w从而控制loss的…

Android Button修改背景颜色及实现科技感效果

目录 效果展示 实现科技感效果 修改Button背景 结语 效果展示 Android Button修改背景颜色及实现科技感效果效果如下&#xff1a; 实现科技感效果 操作方法如下&#xff1a; 想要创建一个富有科技感的按钮样式时&#xff0c;可以使用 Android 的 Shape Drawable 和 Sele…

CSS3表格和表单样式

在传统网页中&#xff0c;表格主要用于网页布局&#xff0c;因此也成为网页编辑的主要工具&#xff1b;在标准化网页设计中&#xff0c;表格的主要功能是显示数据&#xff0c;也可适当辅助结构设计。本章主要介绍如何使用CSS控制表格和表单的显示效果&#xff0c;如表格和表单的…

cortex-A7核UART总线

1.总线 各个部件之间传输一种媒介 2.串行总线 3.并行总线 4.同步和异步 同步&#xff1a; 异步&#xff1a; 5.ST-LINK仿真器连接方式 6.串口通信信息---异步串行全双工总线 串口配置信息8N1代表什么? 7.串口通讯协议

小米电视播放win10视频 win10共享问题

解决的方法就是安装SMB1.0协议 重启就OK了

PyCharm下载和安装教程(包含配置Python解释器)

PyCharm 是 JetBrains 公司&#xff08;www.jetbrains.com&#xff09;研发&#xff0c;用于开发 Python 的 IDE 开发工具。图 1 所示为 JetBrains 公司开发的多款开发工具&#xff0c;其中很多工具都好评如潮&#xff0c;这些工具可以编写 Python、C/C、C#、DSL、Go、Groovy、…

免费的PPT模版--九五小庞

PPT模板&#xff1a; www.1ppt.com/moban/    行业PPT模板&#xff1a;www.1ppt.com/hangye/ 节日PPT模板&#xff1a;www.1ppt.com/jieri/    PPT素材&#xff1a; www.1ppt.com/sucai/PPT背景图片&#xff1a;www.1ppt.com/beijing/   PPT图表&#xff…

C++基础算法④——排序算法(快速、归并附完整代码)

快速排序 快速排序是对冒泡排序的一种改进。 它的基本思想是:通过一趟排序将待排记录分割成独立的两部分&#xff0c;其中一部分记录的关键字均比另一部分记录的关键字小&#xff0c;则可分别对这两部分记录继续进行快速排序&#xff0c;以达到整个序列有序。 假设我们现在对 …

Linux————内置命令大全

&#xff08;一&#xff09;内置命令 Shell 内置命令&#xff0c;就是由 Bash Shell 自身提供的命令&#xff0c;而不是文件系统中的可执行脚本文件。内置命令的执行速度通常优于外部命令&#xff0c;因为执行外部命令不仅会导致磁盘I/O操作&#xff0c;而且还需要为其fork一个…

Android Studio中配置Git

安装Git 在安装Android Studio之前&#xff0c;需要先安装Git。可以从Git官网下载并安装Git&#xff1a;https://git-scm.com/downloads 在Android Studio中配置Git 在Android Studio中&#xff0c;依次点击“File” -> “Settings”&#xff0c;在弹出的窗口中选择“Ver…

设置防火墙

1.RHEL7中的防火墙类型 防火墙只能同时使用一张,firewall底层调用的还是lptables的服务: firewalld:默认 &#xff0c;基于不同的区域做规则 iptables: RHEL6使用&#xff0c;基于链表 Ip6tables Ebtables 2.防火墙的配置方式 查看防火墙状态: rootlinuxidc -]#systemct…

041-第三代软件开发-QCustcomPlot波形标注

第三代软件开发-QCustcomPlot波形标注 文章目录 第三代软件开发-QCustcomPlot波形标注项目介绍QCustcomPlot波形标注效果初始化绘制 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML…

精密数据工匠:探索 Netty ChannelHandler 的奥秘

通过上篇文章&#xff08;Netty入门 — Channel&#xff0c;把握 Netty 通信的命门&#xff09;&#xff0c;我们知道 Channel 是传输数据的通道&#xff0c;但是有了数据&#xff0c;也有数据通道&#xff0c;没有数据加工也是没有意义的&#xff0c;所以今天学习 Netty 的第四…

Windows2008系统怎么隐藏或打开文件后缀

打开服务器的控制面板-选择小图标-文件夹选项 在文件夹选项那边点击查看-隐藏一直文件类型的扩展名 选择勾选&#xff08;隐藏一直文件类型的扩展名&#xff09;-下图示文件后缀不显示 选择不勾选&#xff08;隐藏一直文件类型的扩展名&#xff09;-下图示文件后缀显示