通过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设置的时间有关

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

猜你喜欢

转载自blog.csdn.net/qq_38423256/article/details/134146640