在我们正常项目中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";
}
最后上效果图
菜鸟的学习记录大佬们请指教!