在我们正常项目中session是有存在时间的,这就使的我们在session失效时去通知客户重新去登录!
Shrio框架配置了失效跳转路径但是存在问题就是不能推送通知客户端(可能是我没配对),但是他是可以配合SessionListener和webscoket实现推送通知!

第一步Maven

 <!--WebSocket的支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
		
		<!-- shiro框架 -->
  		<dependency>
			    <groupId>org.apache.shiro</groupId>
			    <artifactId>shiro-spring</artifactId>
			    <version>1.4.0</version>
  		</dependency>

第二步 简单的配置shrio

package com.xk.util;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xk.mapper.UserDao;
import com.xk.model.Users;



public class MyRealm extends AuthorizingRealm {
@Autowired
UserDao userinfo;
		/**
		 * 授权方法,如果不设置缓存管理的话,需要访问需要一定的权限或角色的请求时会进入这个方法
		 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//User users = (User) SecurityUtils.getSubject().getPrincipal();
			Set<String> role = new HashSet<String>(); 
	        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
	        simpleAuthorizationInfo.setStringPermissions(role);
	        return simpleAuthorizationInfo;
	}
	/**
	 * 认证方法
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String loginame = (String) token.getPrincipal();
		QueryWrapper<Users> qu = new QueryWrapper<Users>();
		qu.eq("username", loginame);
		Users thserinfo = userinfo.selectOne(qu);
		 if(thserinfo!=null) {
				SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(thserinfo,thserinfo.getUserpassword(),getName());
		        return authenticationInfo;
		 }else {
			 System.err.println("不存在");
		 }
		
		return null;
	}
	
	  /**
     * 重写方法,清除当前用户的的 授权缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 重写方法,清除当前用户的 认证缓存
     * @param principals
     */
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /**
     * 自定义方法:清除所有 授权缓存
     */
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    /**
     * 自定义方法:清除所有 认证缓存
     */
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    /**
     * 自定义方法:清除所有的  认证缓存  和 授权缓存
     */
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }


}

package com.xk.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {
	 @Bean
	 public MyRealm myRealm() {
		 MyRealm shiroRealm = new MyRealm();
	    return shiroRealm;
	  }
	 @Bean
	   public SessionDAO sessionDAO() {
	        EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO();
	        enterpriseCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator());
	        return enterpriseCacheSessionDAO;
	    }
	    @Bean("sessionManager")
	    public SessionManager sessionManager() {
	        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
	        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
	        //配置监听
	        listeners.add(sessionListener());
	        sessionManager.setSessionIdCookieEnabled(true);
	        sessionManager.setSessionListeners(listeners);
	        sessionManager.setSessionDAO(sessionDAO());
	        //全局会话超时时间(单位毫秒),默认30分钟  暂时设置为10秒钟 用来测试
	        //3000000
	        sessionManager.setGlobalSessionTimeout(10000);
	        //是否开启删除无效的session对象  默认为true
	        sessionManager.setDeleteInvalidSessions(true);
	        //是否开启定时调度器进行检测过期session 默认为true
	        sessionManager.setSessionValidationSchedulerEnabled(true);
	        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
	        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
	        //暂时设置为 5秒 用来测试
	        sessionManager.setSessionValidationInterval(5000);
	        //取消url 后面的 JSESSIONID
	        sessionManager.setSessionIdUrlRewritingEnabled(false);
	        return sessionManager;
	    }

	  @Bean(name = "sessionListener")
	  public ShiroSessionListener sessionListener(){
	        ShiroSessionListener sessionListener = new ShiroSessionListener();
	        return sessionListener;
	    }
	  @Bean(name = "securityManager")
	  public  SecurityManager securityManager() {
	    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
	    manager.setRealm(myRealm());
	    //不加会话管理就不会监听session
	    manager.setSessionManager(sessionManager());
	    return manager;
	  }
	  @Bean(name = "shirFilter")
	    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
		  ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
		    bean.setSecurityManager(securityManager());
		    //session失效触发的路径
		    bean.setLoginUrl("/user/lougout");
		    Map<String, String> map = new LinkedHashMap<>();
		    map.put("/user/mainlayout", "authc");
		    map.put("quartz/*", "anon");
		    map.put("/css/**", "anon");
		    map.put("/uodlod/**", "anon");
		    map.put("img/**", "anon");
		    map.put("/js/**", "anon");
		    map.put("/layui/**", "anon");
		    bean.setFilterChainDefinitionMap(map);
		    return bean;
	    }
	  
	  @Bean
	    public SessionIdGenerator sessionIdGenerator() {
	        return new JavaUuidSessionIdGenerator();
	    }
	    @Bean("sessionIdCookie")
	    public SimpleCookie sessionIdCookie(){
	        //这个参数是cookie的名称
	        SimpleCookie simpleCookie = new SimpleCookie("sid");
	        //setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
	        //setcookie()的第七个参数
	        //设为true后,只能通过http访问,javascript无法访问
	        //防止xss读取cookie
	        simpleCookie.setHttpOnly(true);
	        simpleCookie.setPath("/");
	        //maxAge=-1表示浏览器关闭时失效此Cookie
	        simpleCookie.setMaxAge(-1);
	        return simpleCookie;
	    }
}

继续配置session监听

package com.xk.util;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
/*
 * @description: 配置session监听器,
 */
public class ShiroSessionListener implements SessionListener{

    /**
     * 统计在线人数
     * juc包下线程安全自增
     */
    private final AtomicInteger sessionCount = new AtomicInteger(0);

    /**
     * 会话创建时触发
     * @param session
     */
    @Override
    public void onStart(Session session) {
    	System.err.println("创建了回话");
        sessionCount.incrementAndGet();
    }

    /**
     * 退出会话时触发
     * @param session
     */
    @Override
    public void onStop(Session session) {
        //会话退出,在线人数减一
    	System.err.println("退出回话了");
        sessionCount.decrementAndGet();
    }
    /**
     * 会话过期时触发
     * @param session
     */
    @Override
    public void onExpiration(Session session) {
    	  try {
			WebSocketServer.sendInfo("您长时间没有操作,请重新登录!!!",session.getId().toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	System.err.println("失效");
        sessionCount.decrementAndGet();
    }
    /**
     * 获取在线人数使用
     * @return
     */
    public AtomicInteger getSessionCount() {
        return sessionCount;
    }
}

配置webscoket

package com.xk.util;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
*webscoket配置类
*/
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}


webscoket服务端

package com.xk.util;


import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 *
 * @ServerEndpoint这个注解用于标识作用在类上,它的主要功能是把当前类标识成一个WebSocket的服务端
 * 注解的值用户客户端连接访问的URL
 */
@ServerEndpoint("/websocketserver/{sessionid}")
@Component
public class WebSocketServer {
    /**
     * 静态变量,用来记录当前在线连接数
     */
    private static int count= 0;
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话
     */
    private Session session;
    /**
     * 接收userId
     */
    private String sessionid= "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sessionid") String sessionid) {
        this.session = session;
        this.sessionid= sessionid;
        if (webSocketMap.containsKey(sessionid)) {
            webSocketMap.remove(sessionid);
            webSocketMap.put(sessionid, this);
        } else {
            webSocketMap.put(sessionid, this);
            addcount();
        }
        System.err.println("用户连接:" + sessionid+ ",当前在线人数为:" + getcount());
    }

    /**
     *	 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(sessionid)) {
            webSocketMap.remove(sessionid);
            subcount();
        }
        System.err.println("用户退出:" + sessionid+ ",当前在线人数为:" + getcount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.err.println(sessionid+"该用户上线"+message);
        //可以群发消息
        //消息保存到数据库、redis
        if (message!=null) {
            try {
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                //追加发送人(防止串改)
                jsonObject.put("fromUserId", this.sessionid);
                String toUserId = jsonObject.getString("toUserId");
                //传送给对应toUserId用户的websocket
                if (toUserId!=null && webSocketMap.containsKey(toUserId)) {
                    webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                } else {
                	//否则不在这个服务器上
                	 System.err.println("请求的userId:" + toUserId + "不在该服务器上或不存在该地址");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
    	 System.err.println("用户错误:" + this.sessionid+ ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
       * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     	* 发送自定义消息
     */
    public static void sendInfo(String message, @PathParam("sessionid") String sessionid) throws IOException {
        System.err.println("发送消息到:" + sessionid+ ",报文:" + message);
        if (userId!=null && webSocketMap.containsKey(sessionid)) {
            webSocketMap.get(sessionid).sendMessage(message);
        } else {
            System.err.println("用户" + userId + ",不在线!");
        }
    }

    public static synchronized int getcount() {
        return count;
    }

    public static synchronized void addcount() {
        WebSocketServer.count++;
    }

    public static synchronized void subcount() {
        WebSocketServer.count--;
    }
}


前端页面注意放到框架的main(登录成功跳转的主页面)页面

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Insert title here</title>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<link rel="stylesheet" href="${pageContext.request.contextPath}/layui/css/layui.css" />
<script type="text/javascript" src="${pageContext.request.contextPath}/layui/layui.js" ></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/layui/layui.js" ></script>
<link rel="stylesheet" href="../nprogress/nprogress.css">
<link rel="stylesheet" href="../layui/css/layui.css">
<link rel="stylesheet" href="../css/scroll-bar.css">
<link rel="stylesheet" href="../css/sub-page.css">
</head>
<body>

<button style="margin-left: 35%;margin-top: 10%;" class="layui-btn xkdiv" id="exprot">导出excle演示</button>

<button  style="margin-left: 10%;margin-top: 10%;"  class="layui-btn xkdiv" id="task">定时任务演示</button>
<input type="hidden" id="id" value="${sessionid }"> 
<input type="hidden" id="ippath" value="${serverIpv4 }"> 
</body>
<script type="text/javascript">
layui.use('layer', function () {
	 var layer = layui.layer;//加载layer模块
function timeout(msgs) {
	layer.alert(msgs,{
		icon:5,
		title:'系统提示',
		 skin: 'layui-layer-molv',
		 closeBtn: 0
	}, function(index) {
		location.href = "${pageContext.request.contextPath}/user/lougout";
	});
}
	 
var socket;
if(typeof(WebSocket) == "undefined") {
	console.log("浏览器不支持WebSocket");
}else{
	console.log("浏览器支持WebSocket");
	//实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
	//ws://localhost:8082/imserver/888
	var socketUrl="http://"+$("#ippath").val()+"/websocketserver/"+$("#id").val();
	socketUrl=socketUrl.replace("https","ws").replace("http","ws");
 if(socket!=null){
		socket.close();
		socket=null;
	} 
	socket = new WebSocket(socketUrl);
	//打开事件
	socket.onopen = function() {
		console.log("websocket已打开");
	};
	//获得消息事件
	socket.onmessage = function(msg) {
		console.log(msg.data);
		//发现消息进入    开始处理前端触发逻辑
		timeout(msg.data);
	};
	//关闭事件
	socket.onclose = function() {
		console.log("websocket已关闭");
	};
	//发生了错误事件
	socket.onerror = function() {
		console.log("websocket发生了错误");
	}
}
})
</script>
<script type="text/javascript">
$("#exprot").click(function () {
	window.location="${pageContext.request.contextPath}/user/exprot";
})

$("#task").click(function () {
	window.location="${pageContext.request.contextPath}/user/task";
})
</script>
</html>

Controller

  @RequestMapping("mainlayout")
	  public String mains(HttpServletRequest request,Map<String,Object> map) {
		  String id = request.getSession().getId();
		  map.put("sessionid", id);
		  //获取服务端的IP地址很简单的百度下都有
		  String serverIpv4 = GetServerip.ServerIpv4();
		  map.put("serverIpv4", serverIpv4+":1525");
		  return "mainlayout";
	  }

最后上效果图

session失效时的通知

				菜鸟的学习记录大佬们请指教!
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐