1. Предпосылки
Существуют различные схемы разделения сеансов, которые были написаны ранее.«Spring Session реализует совместное использование сеансов кластера Tomcat»Статья, функция очень проста и удобна в реализации.
Недавно изучив фреймворк Shiro, Широ также предоставляет функцию управления сессиями. Если Shiro выбран в качестве схемы управления разрешениями в проекте, а проекту нужен кластер, то sessionDAO можно настроить для реализации совместного использования сеансов.
2. Реализация
JDK: 1.8
Контейнер: Томкэт 8
Контейнер хранилища сеансов: Redis 3.2.0
Тестовая среда такая же, как и при тестировании Spring Session, разверните проект на 2 tomcats на одной виртуальной машине и начните использовать порты 8080 и 8081.
Основная конфигурация указана ниже. Конфигурация jar и рабочая конфигурация, от которых зависит Shiro, игнорируются. Конкретный код можно загрузить и просмотреть из исходного кода, представленного ниже.
2.1 applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<context:component-scan base-package="com.light.dao.*"/>
<!-- redis 连接池 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="20"></property>
<property name="maxIdle" value="1"></property>
</bean>
<!-- redis 连接工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="hostName" value="192.168.2.11"/>
<property name="port" value="6379"/>
<property name="timeout" value="5000"/>
<property name="password" value=""/>
<property name="usePool" value="true"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
</bean>
<!-- redis 模板 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean >
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/index.jsp" />
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
/resources/**=anon
/login=anon
</value>
</property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="sessionDAO"></property>
</bean>
<!-- 自定义 sessionDAO -->
<bean id="sessionDAO" class="com.light.dao.CustomSessionDAO"></bean>
</beans>
2.2 Пользовательский сеансDAO
Пользовательский sessionDAO должен наследовать класс AbstractSessionDAO, чтобы переопределить сеанс CRUD.
public class CustomSessionDAO extends AbstractSessionDAO {
private static final int EXPIRE_TIME = 600;
@Resource(name="redisTemplate")
private RedisTemplate<String,Object> redisTemplate;
public void update(Session session) throws UnknownSessionException {
this.redisTemplate.opsForValue().set(
session.getId().toString(),
session,
EXPIRE_TIME,
TimeUnit.SECONDS);
}
public void delete(Session session) {
this.redisTemplate.delete(session.getId().toString());
}
public Collection<Session> getActiveSessions() {
// TODO
return null;
}
@Override
protected Serializable doCreate(Session session) {
// 生成 sessionId
Serializable sessionId = this.generateSessionId(session);
// session 绑定 sessionId
this.assignSessionId(session, sessionId);
this.redisTemplate.opsForValue().set(
session.getId().toString(),
session,
EXPIRE_TIME,
TimeUnit.SECONDS);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
Session session = (Session) this.redisTemplate.opsForValue().get(sessionId.toString());
if (session != null) {
this.redisTemplate.opsForValue().set(
session.getId().toString(),
session,
EXPIRE_TIME,
TimeUnit.SECONDS);
}
return session;
}
}
Класс CustomSessionDAO является основой для реализации совместного использования сеансов.
3.3 Внутренний код
@Controller
public class LoginController {
@Autowired
private SecurityManager sm;
@RequestMapping("login")
public String login(String userName, String password,HttpServletRequest request) {
// 首次登录
if ("admin".equals(userName) && "admin".equals(password)) {
SecurityUtils.setSecurityManager(sm);
Subject subject = SecurityUtils.getSubject();
// 使用 shiro 的 session 保存数据
Session session = subject.getSession();
session.setAttribute("userName", userName);
return "manageUI";
}
// 如果已经登录过,从另一个 tomcat 访问该方法,跳转到 manageUI 页面可以查看 session 信息
if ("".equals(userName) && "".equals(password)) {
return "manageUI";
}
return "redirect:/index.jsp";
}
@RequestMapping("logout")
public String logout(HttpSession session) {
session.removeAttribute("userName");
session.removeAttribute("url");
return "redirect:/index.jsp";
}
}
Примечание. Внутренний код использует сеансовый API, предоставленный Широ, для сохранения данных.
3.4 Интерфейсный код
Страница index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>登陆界面</title>
<link href="/resources/css/bootstrap.min.css" rel="stylesheet">
<style>
html {
background: url("/resources/images/bg.png") no-repeat center center;
}
label {
color: #fff;
}
.container {
position:absolute;
top:50%;
left:50%;
margin-top: -115px;
margin-left: -250px;
width: 500px;
height:230px;
padding:50px;
border: 2px solid #eee;
border-radius: 5px;
box-shadow:5px 5px 16px #000;
}
</style>
</head>
<body>
<div class="container">
<form class="form-horizontal" role="form" action="/login" method="post">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="userName" placeholder="用户名">
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password" placeholder="密码">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" style="width: 100%">登陆</button>
</div>
</div>
</form>
</div>
</body>
</html>
Страница manageUI.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>管理界面</title>
<link href="/resources/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="jumbotron">
<h3>测试 Shiro 实现 session 共享</h3>
<h3>端口为 8080 的页面</h3>
<h3>用户名:${sessionScope.userName}(session 域数据)</h3>
<p><a class="btn btn-lg btn-success" href="/logout" role="button">注销</a></p>
</div>
</div>
</body>
</html>
Примечание. Страницу проекта 8081 необходимо изменить на «страницу с портом 8081».
3. Демонстрация
Шаги тестирования такие же, как и при тестировании Spring Session.
ожидаемый результат:
1) Сначала войдите в проект через порт 8080 и войдите в систему, перейдите к интерфейсу управления и отобразите сохраненную информацию.
2) Посетите страницу проекта порта 8081 в том же браузере, нажмите кнопку входа в систему, не вводя пароль учетной записи, и он перейдет непосредственно к интерфейсу управления. Если сеанс является общим, вы можете просмотреть информацию, сохраненную в сеансе проектом порта 8080, в интерфейсе управления. Иначе наоборот.
Демонстрационная схема выглядит следующим образом:
В целом реализация функции несложная, но более хлопотная, чем использование решения сеанса Spring, потому что разработчикам нужно самим реализовать CRUD сеанса. Просто потому, что это необходимо реализовать вручную, более гибко использовать схему Shiro для управления сеансами.