The background,
There are many schemes for Session sharing. I wrote the article “Spring Session Implements Session Sharing for Tomcat Cluster” before, which is very simple and convenient to implement.
Recently in learning Shiro framework, Shiro also provides session management capabilities. If Shiro is chosen as the permission control solution for your project and the project needs clustering, you can customize the sessionDAO to implement Session sharing.
Second, the implementation
JDK: 1.8 Container: Tomcat 8 Session Storage container: Redis 3.2.0
The test environment is the same as when testing Spring Session. The project is deployed to two Tomcats on the same virtual machine and started using ports 8080 and 8081.
The main configurations are listed below. The JAR configurations Shiro relies on and the run configurations are ignored. The code can be downloaded from the source code provided below.
2.1 applicationContext – shiro. XML
<? The 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 connection pool - > < bean id = "jedisPoolConfig" class = "redis. Clients. Jedis. JedisPoolConfig" > < property name = "maxTotal" value="20"></property> <property name="maxIdle" value="1"></property> </bean> <! <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" destroy-method="destroy"> <property 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 template - > < bean id = "redisTemplate" class = "org. Springframework. Data. Redis. Core. RedisTemplate" > < property name="connectionFactory" ref="jedisConnectionFactory" /> </bean > <! - Shiro's Web filters - > < bean id = "shiroFilter" class = ". Org. Apache Shiro. Spring. Web. ShiroFilterFactoryBean "> < property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/index.jsp" /> <! -- Filter chain definition, executed from top to bottom, < name="filterChainDefinitions"> <value> /resources/**=anon /login=anon <value> </value> </bean> <! - security manager - > < bean id = "securityManager" class = ". Org. Apache shiro. Web. MGT. DefaultWebSecurityManager "> < property name="sessionManager" ref="sessionManager" /> </bean> <! - the session manager - > < bean id = "our sessionManager" class = ". Org. Apache shiro. Web. Session. MGT. DefaultWebSessionManager "> < property name="sessionDAO" ref="sessionDAO"></property> </bean> <! - custom sessionDAO - > < bean id = "sessionDAO" class = "com. Light. Dao. CustomSessionDAO" > < / bean > < / beans >Copy the code
2.2 Customizing the sessionDAO
The custom sessionDAO needs to inherit the AbstractSessionDAO class to override the SESSION 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) {// Generate Session ID Serializable sessionId = this.generateSessionId(session); // Session binding 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; }}Copy the code
The CustomSessionDAO class is the core of session sharing.
3.3 Back-end code
@Controller public class LoginController { @Autowired private SecurityManager sm; @RequestMapping("login") public String login(String userName, String password, it request) {/ / login for the first time the if (" admin ". The equals (userName) && "admin" equals (password)) { SecurityUtils.setSecurityManager(sm); Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); session.setAttribute("userName", userName); return "manageUI"; } // If already logged in, access the method from another Tomcat, 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"; }}Copy the code
Note: The back-end code uses the Session API provided by Shiro to save data.
3.4 Front-end Code
The index. The JSP page:
<%@ 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 =1"> <meta name="description" content=""> <meta name="author" content=""> <title 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"> User name </label> <div Class ="col-sm-10"> <input type="text" class="form-control" name="userName" placeholder=" userName" > </div> </div> <div Class ="form-group"> <label for="inputPassword3" class=" col-SM-2 control-label"> password </label> <div class=" col-SM-10 "> <input Type ="password" class="form-control" name="password" placeholder=" password" > </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% "> log in < / button > < / div > < / div > < / form > < / div > < / body > < / HTML >Copy the code
ManageUI. JSP page:
<%@ 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>Copy the code
Note: the page of the 8081 project needs to be changed to “page with port 8081”.
Three, presentations,
The test steps are the same as when testing Spring Session.
Expected effect:
1) First access the project of port 8080 and log in, jump to the management interface and display the saved information.
2) When accessing the page of port 8081 project in the same browser, you do not need to enter the account password and click the login button directly to jump to the management interface. If the session is shared, you can view the information stored in the session by port 8080 project in the admin interface. Otherwise, vice versa.
The demo diagram is as follows:
Overall, the functionality is not too difficult to implement, but it is a bit more cumbersome than using the Spring Session scheme because you need to implement session CRUD yourself. On the other hand, it is more flexible to use Shiro to manage sessions because of the manual implementation.