前言

先贴一个项目地址 GitHub-Springboot-shiro-Redis
全文基于Maven进行管理
本文涉及范围
1.shiro在Springboot的共享Session配置
2.Shiro单用户登录的配置
3.Spring-data-jpa的部分不会赘述

个人学习研究,目前未在生产环境使用,有好的意见欢迎评论

共享Session的应用场景

应用场景

当用户访问系统服务时,会经过负载均衡,根据配置策略的不同,同一客户端访问的最终服务器可能不是同一台,为了保证用户的Session状态连续,则需要集群内各业务服务器共享Session。

目录

1.Maven 依赖引入shiro-spring-boot-web-starter
2.Shiro 的基本配置
3.Maven 依赖引入 spring-boot-starter-data-redis
4.针对集群共享需要进行的Shiro 扩展
5.集群共享Session下的单用户登录

1.Maven 依赖引入shiro-spring-boot-web-starter

编辑pom.xml文件

<dependencies><!--······your dependencies --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.4.2</version></dependency>
</dependencies>

2.Shiro 的基本配置

这块可以参照Shiro的官方文档
Integrating Apache Shiro into Spring-Boot Applications

根据文档,我们为了实现shiro接管web应用的访问路径,仅需要实现一个自定义Realm并注入Spring即可

2.1 创建自定义CustomeRealm继承AuthorizingRealm

主要是实现2个继承的方法

AuthenticationInfo :登录验证 定义判断用户的登录信息是否正确

doGetAuthorizationInfo:授权方法 定义如何获取用户的角色和权限的逻辑,给shiro做权限判断

CustomeRealm.java

class CustomRealm extends AuthorizingRealm {@AutowiredUserService userService  //用户对象的管理服务类,提供CURD操作@AutowiredAuthService authService  //权限验证服务类,可根据用户获取相应role和permission/*** 定义如何获取用户信息的业务逻辑,给shiro做登录* @param token 登录TOKEN,包含了用户账号密码* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//登录TOKEN,包含了用户账号密码UsernamePasswordToken upToken = (UsernamePasswordToken) token;String username = upToken.getUsername();//下列多个判断可根据业务自行增删// 判断用户名是否不存在,如果不存在抛出异常if (username == null) {throw new AccountException("Null usernames are not allowed by this realm.");}//通过用户名,从数据库中查询出用户信息User user = userService.findUserByName(username);//如果用户不存在,则抛出账号不存在异常,由控制器决定返回消息为账号或密码错误if (user == null) {throw new UnknownAccountException("No account found for admin [" + username + "]");}// 如果用户账号为锁定状态,则不予登录。if (user.isLocked()) {throw new LockedAccountException("Account [" + username + "] is locked.");}//如果账号超出有效期,则不予登录if (user.isCredentialsExpired()) {String msg = "The credentials for account [" + username + "] are expired";throw new ExpiredCredentialsException(msg);}//查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方//SecurityUtils.getSubject().getPrincipal() 就能拿出用户的所有信息,包括角色和权限/** 将用户权限和角色存入User对象*/user.setRoles(new HashSet<String>(["admin","teacher"]))user.setPerms(new HashSet<String>(["blog:read","blog:search"]))//构造验证信息返回SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.password, getName())return info}/*** 授权* 定义如何获取用户的角色和权限的逻辑,返回包含用户角色和许可信息* @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//null usernames are invalidif (principals == null) {throw new AuthorizationException("PrincipalCollection method argument cannot be null.");}//获取当前用户对应的User对象User user = (User) getAvailablePrincipal(principals);//创建权限对象SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//设置用户角色(user.getRoles()是一个Set<String>,【admin,student。。。】)info.setRoles(user.getRoles())//设置用户许可(user.getPerms()是一个Set<String>,【blog:read,blog:search。。。】)info.setStringPermissions(user.getPerms())return info}
}

2.2 将CustomeRealm注入Spring

1.首先将CustomeRealm 配置为Bean
2.然后将CustomeRealm注入到DefaultWebSecurityManager

创建配置对象ShiroConfig.java

@Configuration
class ShiroConfig {/*** 注入自定义权限验证对象*/@Beanpublic CustomRealm customRealm() {CustomRealm realm = new CustomRealm();return new CustomRealm();}/*** SecurityManager是Shiro框架的核心,典型的Facade模式,* Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务* 将自定义CustomRealm 注入进SecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(CustomRealm customRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//自定义realmsecurityManager.setRealm(customRealm);return securityManager;}  /*** 为了保证实现了Shiro内部lifecycle函数的bean执行 也是shiro的生命周期*/@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();/*** setUsePrefix(true)用于解决一个奇怪的bug。在引入spring aop的情况下。* 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,* 导致返回404。加入这项配置能解决这个bug*/defaultAdvisorAutoProxyCreator.setUsePrefix(true);return defaultAdvisorAutoProxyCreator;}/***  shiro的统一权限判定*/@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition(RequestMapService requestMapService) {DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();//设置所有路径均不需要登录,可在控制器中添加Shiro注解进行覆盖chain.addPathDefinition("/**", "anon");return chain;}
}

2.3 通过Http header 传递SessionID(如无需要可略过)

如果前端通过Http协议的Header进行SessionID的发送
则需要实现一个继承DefaultWebSessionManager的自定义SessionManager
然后注入DefaultWebSecurityManager

2.3.1 创建CustomeSessionManager.java

/***  @author Hoody* 自定义sessionId获取方式* 从前端发送的header中获取SessionId,如果没有再从cookie中读取*/
class CustomSessionManager extends DefaultWebSessionManager {/** 存放 sessionID 的header key */private static final String AUTHORIZATION = "X-Token"private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"/*** 重写getSessionId方法, 从前端发送的header中获取SessionId,如果没有再从cookie中读取* @param request* @param response* @return*/@Overrideprotected Serializable getSessionId(ServletRequest request, ServletResponse response) {String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION)//如果请求头中有 Authorization 则其值为sessionIdif (!StringUtils.isEmpty(id)) {request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE)request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id)request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE)return id} else {//否则按默认规则从cookie取sessionIdreturn super.getSessionId(request, response)}}
}

2.3.2 注入CustomeSessionManagerDefaultWebSecurityManager

ShiroConfig.java 中添加CustomeSessionManagerBean的注入

@Beanpublic SessionManager sessionManager() {CustomSessionManager customSessionManager = new CustomSessionManager();return customSessionManager;}

修改ShiroConfig.java,将CustomSessionManager添加到securityManager

/*** SecurityManager是Shiro框架的核心,典型的Facade模式,* Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务* 将自定义CustomRealm 注入进SecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(CustomRealm customRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//自定义realmsecurityManager.setRealm(customRealm);//自定义session管理securityManager.setSessionManager(sessionManager());return securityManager;}

3.Maven 依赖引入 spring-boot-starter-data-redis

关于Redis的部分不做赘述

3.1在pom.xml加入如下依赖

<!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

3.2 application.yml配置

spring:redis:host: localhost #redis服务PIport: 6379      #服务端口

3.3 Redis 的基本操作

@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//保存redisTemplate.opsForValue().set("key-1", "value-1"); //带有效期的保存redisTemplate.opsForValue().set("key-1", "value-1", 120, TimeUnit.SECONDS);//删除redisTemplate.delete("key-1");

4.针对集群共享需要进行的Shiro 扩展

根据官方文档Shiro-Session Storage,如果要自定义Session的存储
需要自己实现一个SessionDao对象来扩展Session的CURD

4.1 创建RedisSessionDAO 继承CachingSessionDAO

需要Override的4个方法是:doCreate: shiro创建session时,将session保存到redisdoUpdate: 当用户维持会话时,刷新session的有效时间doDelete: 当用户注销或会话过期时,将session从redis中删除doReadSession: shiro通过sessionId获取Session对象,从redis中获取

创建 RedisSessionDAO.java

public class RedisSessionDAO extends CachingSessionDAO {//存入Redis中的SessionID的前缀private static final String PREFIX = "SHIRO_SESSION_ID";//有效期(后续使用时会增加时间单位)private static final int EXPRIE = 1200;//Redis 操作工具 详情见本文3.3章节private RedisTemplate<Serializable, Session> redisTemplate;//构造函数public RedisSessionDAO(RedisTemplate<Serializable, Session> redisTemplate) {this.redisTemplate = redisTemplate;}/*** shiro创建session时,将session保存到redis* @param session* @return*/@Overrideprotected Serializable doCreate(Session session) {//生成SessionIDSerializable serializable = this.generateSessionId(session);assignSessionId(session, serializable);//将sessionid作为Key,session作为value存入redisredisTemplate.opsForValue().set(serializable, session);return serializable;}/*** 当用户维持会话时,刷新session的有效时间* @param session*/@Overrideprotected void doUpdate(Session session) {//设置session有效期session.setTimeout(EXPRIE * 1000);//将sessionid作为Key,session作为value存入redis,并设置有效期redisTemplate.opsForValue().set(session.getId(), session, EXPRIE, TimeUnit.SECONDS);}/*** 当用户注销或会话过期时,将session从redis中删除* @param session*/@Overrideprotected void doDelete(Session session) {//null 验证if (session == null) {return;}//从Redis中删除指定SessionId的k-vredisTemplate.delete(session.getId());}/***  shiro通过sessionId获取Session对象,从redis中获取  * @param sessionId* @return*/@Overrideprotected Session doReadSession(Serializable sessionId) {if (sessionId == null) {return null;}//从Redis中读取Session对象Session session = redisTemplate.opsForValue().get(sessionId);return session;}
}

4.2 将RedisSessionManager注入 SecurityManager

2个步骤:
1.容器中注册RedisSessionDao
2.获取SessionManager,并将自定义sessionDAO设置进去

编辑 ShiroConfig.java

//容器中注册RedisSessionDao@Beanpublic SessionDAO redisSessionDAO(RedisTemplate redisTemplate) {return new RedisSessionDAO(redisTemplate);}/*** 将SessionDao 加入 SecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(CustomRealm customRealm,SessionDAO sessionDAO) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//自定义realmsecurityManager.setRealm(customRealm);//获取SessionManagerDefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager();//设置自定义sessionDAOsessionManager.setSessionDAO(sessionDAO);return securityManager;}

至此,已经完成Shiro的集群共享Session

Service层代码如下

/*** 登录* @param UsernamePasswordToken  token */public Session login(String username,String password) {Subject currentUser = SecurityUtils.getSubject()currentUser.login(new UsernamePasswordToken(username, password));currentUser.login(token)//从session中取出用户User user = (User) currentUser.getPrincipal()if (user == null) throw new AuthenticationException()//返回登录用户的信息给前台,含用户的所有角色和权限return currentUser.getSession()}

单机测试,可以通过启动应用登录后,重启应用,然后携带Session继续访问服务器即可

5. 集群共享Session下的单用户登录

单用户登录即单一账号,只能在一处登录,系统中不允许多个用户登录同一账号。

5.1 思路

1.用户登录时,在Redis中查询有没有以改账号登录的Session,如果没有则直接登录;如果有则删除已登录Session,达到踢出上一登录的目的。

2.目前用户登录信息Session保存在Redis中,各服务器均可操作,但是Redis中仅能通过SessionID进行查询,所以需要将用户名与当前SessionId进行关联。

3. 通过RedisSessionDao进行Session操作的时候可将用户名SessionId进行关联保存到Redis。

综上,需要对以下部分进行改造

1.RedisSessionDao中参数为SimpleSession对象,不包含用户名等信息,所以需要扩展创建自定义CustomeSession对象,增加用户名信息等。
2. Session 对象由Shiro的SessionFactory提供,所以也要重写一个自定义Session工厂类CustomSessionFactory,并在其中创建CustomeSession,添加用户名信息。
3. 在CustomeRealm中添加Session判定与删除操作,调用RedisSessionDAO查找是否已经有用户通过该账号登录,如果有则踢出上一处登录Session。
4. RedisSessionDAO 中增加方法getSessionByUsername,访问Redis查询Session

5.2 创建自定义CustomeSession

我这里只增加了Username信息,根据需要可自行扩展

CustomeSession.java

/*** 自定义Session ,增加了用户名信息*/
public class CustomSession extends SimpleSession {private String usernmae;public String getUsernmae() {return usernmae;}public void setUsernmae(String usernmae) {this.usernmae = usernmae;}public CustomSession() {this.usernmae = null;}public CustomSession(String host, String usernmae) {super(host);this.usernmae = usernmae;}
}

5.3 创建自定义CustomSessionFactory

SessionFactory仅需要实现一个创建Session的方法即可。

/*** 自定义SessionFactory* 提供CustomSession的创建接口实现*/
public class CustomSessionFactory implements SessionFactory {@Overridepublic Session createSession(SessionContext initData) {if (initData != null) {String host = initData.getHost();//通过initData获取到登录的参数,getParameter("username"); ,key值根据前端请求确定String username = ((DefaultWebSessionContext) initData).getServletRequest().getParameter("username");//如果不是匿名登录则创建包含信息的Sessionif (host != null && username != null) {return new CustomSession(host, username);}}//匿名访问,直接创建空Sessionreturn new CustomSession();}
}

CustomSessionFactory注入Shiro

修改 ShiroConfig.java

//注册Bean@Beanpublic CustomSessionFactory customSessionFactory() {return new CustomSessionFactory();}/*** 将SessionDao 加入 SecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(CustomRealm customRealm,CustomSessionFactory customSessionFactory,SessionDAO sessionDAO) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//自定义realmsecurityManager.setRealm(customRealm);//获取SessionManagerDefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager();//设置自定义sessionDAOsessionManager.setSessionDAO(sessionDAO);//设置自定义SessionFactorysessionManager.setSessionFactory(customSessionFactory);return securityManager;}

5.4 CustomeRealm中增加 session判定

在登录账号、有效期等验证之后加入checkIsLogin(token)处理

checkIsLogin
1.通过SecurityUtils获取到SessionDao
2.通过username查询是否已经存在登录的Session
3.如果存在,则从Shiro中删除Session

修改 CustomeRealm.java

/*** 定义如何获取用户信息的业务逻辑,给shiro做登录* @param token 登录TOKEN,包含了用户账号密码* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//登录TOKEN,包含了用户账号密码UsernamePasswordToken upToken = (UsernamePasswordToken) token;String username = upToken.getUsername();//下列多个判断可根据业务自行增删//''''''省略其他判断 ,省略部分参考本文 2.1章节//通过此方法对session进行单用户处理this.checkIsLogin(upToken)//查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方//SecurityUtils.getSubject().getPrincipal() 就能拿出用户的所有信息,包括角色和权限/** 将用户权限和角色存入User对象*/user.setRoles(new HashSet<String>(["admin","teacher"]))user.setPerms(new HashSet<String>(["blog:read","blog:search"]))//构造验证信息返回SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.password, getName())return info}/***  单用户登录判断*  1.通过SecurityUtils获取到SessionDao*  2.通过username查询是否已经存在登录的Session*  3.如果存在,则从Shiro中删除Session* @param token*/ private void checkIsLogin(UsernamePasswordToken token) {DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager()DefaultWebSessionManager sessionManager = (DefaultWebSessionManager) securityManager.getSessionManager()RedisSessionDAO sessionDAO = (RedisSessionDAO) sessionManager.getSessionDAO()Session session = sessionDAO.getSessionByUsername(token.getUsername())if (session != null) {sessionDAO.delete(session);}}

5.5 RedisSessionDAO 中增加方法getSessionByUsername,访问Redis查询Session

在session的CURD的几个步骤中加入 Username与SessionID 的键值对处理

修改完成后的RedisSessionDAO.java,主要注意USERNAME_PREFIX相关的操作

public class RedisSessionDAO extends CachingSessionDAO implements CacheManagerAware {//Redis存储Session的key前缀private static final String PREFIX = "SHIRO_SESSION_ID";//Redis存储Username与SessionID 的key前缀private static final String USERNAME_PREFIX = "USERNAME_SESSION_ID";//过期有效期private static final int EXPRIE = 10000;//Redis操作工具类private RedisTemplate<Serializable, Object> redisTemplate;//构造函数public RedisSessionDAO(RedisTemplate<Serializable, Object> redisTemplate) {this.redisTemplate = redisTemplate;}//创建session@Overrideprotected Serializable doCreate(Session session) {//通过Session转型为自定义SessionCustomSession customSession = (CustomSession) session;//生成SessionIdSerializable serializable = this.generateSessionId(session);assignSessionId(session, serializable);//保存k-v:sessionID-Session对象到 RedisredisTemplate.opsForValue().set(PREFIX + serializable, session);//判断如果当前用户不是是匿名登录if (customSession.getUsernmae() != null) {//在Redis中保存 键值对 用户名-SessionIDredisTemplate.opsForValue().set(USERNAME_PREFIX + customSession.getUsernmae(), serializable);}return serializable;}//更新session有效期@Overrideprotected void doUpdate(Session session) {session.setTimeout(EXPRIE * 1000);CustomSession customSession = (CustomSession) session;//将sessionid作为Key,session作为value存入redis,并设置有效期redisTemplate.opsForValue().set(PREFIX + session.getId(), session, EXPRIE, TimeUnit.SECONDS);//判断如果当前用户不是是匿名登录if (customSession.getUsernmae() != null) {//在Redis中更新 键值对 用户名-SessionID的有效期redisTemplate.opsForValue().set(USERNAME_PREFIX + customSession.getUsernmae(), session.getId(), EXPRIE, TimeUnit.SECONDS);}}@Overrideprotected void doDelete(Session session) {if (session == null) {return;}CustomSession customSession = (CustomSession) session;redisTemplate.delete(PREFIX + session.getId());//判断如果当前用户不是是匿名登录if (customSession.getUsernmae() != null) {/在Redis中删除 键值对 用户名-SessionIDredisTemplate.delete(USERNAME_PREFIX + customSession.getUsernmae());}}/*** 从Redis读取Session,* 如果未读取到,有2种情况,Session过期,或者被重新登录踢出,则抛出异常* @param sessionId* @return*/@Overrideprotected Session doReadSession(Serializable sessionId) {if (sessionId == null) {return null;}//尝试读取SessionSession session = (Session) redisTemplate.opsForValue().get(PREFIX + sessionId);//如果未读取到有2种情况,Session过期,或者被重新登录踢出,则抛出异常if (session == null) {throw new SignOutException("Account Sign in offsite");}return session;}//根据用户名获取Sessionpublic Session getSessionByUsername(String username) {String sessionId = this.getSessionIdByUsername(username);return doReadSession(sessionId);}//根据用户名获取SessionIdpublic String getSessionIdByUsername(String username) {return (String) redisTemplate.opsForValue().get(USERNAME_PREFIX + username);}
}

6.总结

很早之前接触了Shiro,但是都是单机应用,最近在尝试了解分布式与集群相关的东西。所以尝试写了这个文档作为从Springboot迈向下一步的记录。 希望能够帮助到你。 总的来说还是要多看看看官方文档,阅读源码

项目GitHub地址:

HoodyHuo/Springboot-shiro-SwaggerUI-template​github.com

原文发布于我的个人Blog:

Shiro Springboot 集群共享Session (Redis)+单用户登录​hoody.tech

admin登录 404_Shiro Springboot 集群共享Session (Redis)+单用户登录相关推荐

  1. 集群共享session;shiro实现session共享;springboot实现redis共享session;

    shiro实现共享session;springboot集成redis共享session;集群环境下shiro共享session 一.实现session共享 1. 聊聊session共享 2. shir ...

  2. tomcat集群共享session

    [前言] 无数人撞的头破血流告诉我们的一个经验是一个网站如果不做负载后果是不堪设想:负载一是对请求进行分压不至于大流量过来把机器压垮,即使部分机器Down掉网站仍可用(高可用,防止单点故障). [负载 ...

  3. asp.net mvc 用Redis实现分布式集群共享Session。

    1.这两天研究Redis搞分布式session问题,网上找的资料都是用ServiceStack.Redis来实现的,但是在做性能测试的时候发现最新的v4版本有限制每小时候最多请求6000次,因为官网开 ...

  4. Tomcat7集群共享Session 基于redis进行统一管理

    背景: 很多时候,生产环境,需要多个tomcat协作,那么session的统一管理是一个首先需要解决的问题.session的统一管理有很多解决方案,比如存储至数据库.memcache.redis.那么 ...

  5. Tomcat7集群共享Session 基于redis进行统一管理(转)

    背景: 很多时候,生产环境,需要多个tomcat协作,那么session的统一管理是一个首先需要解决的问题.session的统一管理有很多解决方案,比如存储至数据库.memcache.redis.那么 ...

  6. 通过memcached来实现对tomcat集群中Session的共享策略 .

    近期在做一套集群的实现,实现的方案是在Linux下完成对Apache + Tomcat 负载均衡的功能. 但是实现了该集群后,发现登陆系统后,每次都会被拦截回登录页面,造成该现象的原因是Session ...

  7. Tomcat集群及Session共享

    Tomcat集群 由于单台Tomcat的承载能力是有限的,当我们的业务系统用户量比较大,请求压力比较大时,单台Tomcat是扛不住的,这个时候,就需要搭建Tomcat的集群,而目前比较流行的做法就是通 ...

  8. keycloak php,keycloak集群配置session共享

    前言 本文主要记录一下配置keycloak集群中session共享的过程. 问题出现 在之前的一篇博文HAproxy的Sticky Session中曾经提到过用户的登录请求在多台keycloak之间跳 ...

  9. Redis + Tomcat + Nginx 集群实现 Session 共享

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者 | 蕃薯耀 链接 | www.cnblogs.com/fan ...

  10. Nginx+tomcat集群的session共享问题

    请求负载过程中会话信息不能丢失.那么需要在多个tomcat中session需要共享.所以需要进行相关问题的解决 配置Tomcat的session共享可以有三种解决方案: 第一种:是以负载均衡服务器本身 ...

最新文章

  1. C#中throw抛出异常后,后续代码执行情况
  2. Qt之对话框设计——淡入淡出效果
  3. 四屏带缩略图JS幻灯片
  4. Code Forces 448C Painting Fence 贪婪的递归
  5. 在Linux中查找用户帐户信息和登录详细信息的11种方法
  6. 云服务器上部署pytorch,flask部署pytorch-服务端
  7. UIPIckerView现实城市选择
  8. .NET Interop.SHDocVw和MSHTML引用如何操作
  9. 软件开发过程与项目管理(7.软件项目进度计划)
  10. python numpy官网_如果通过官网下载Python-Numpy的文档
  11. 计算机xp系统ie8,教你能够完全windows XP下IE8的方法
  12. 2021计算机会议截稿时间,ICCV 2021即将截稿、NeurIPS 大会征稿通知... | AI 顶会动态一览...
  13. 数据库应用系统的生命周期
  14. 薇娅,李佳琦都点赞的淘宝双11直播系统,是如何打造的?
  15. html简单购物车,用jsp实现一个简单的购物车web应用系统。
  16. 全系列毕业设计论文来了
  17. 华为mate7 Android 7,华为Mate7屏幕怎么样?华为Mate7屏幕材质是什么?
  18. c语言中如何输入log函数,在C语言中使用对数函数的方法
  19. 美国能限制linux内核,因故意引入漏洞,美国一所大学被禁止为 Linux 内核做贡献...
  20. 我的世界html启动器资源,我的世界hmcl启动器mod

热门文章

  1. classpath路径浅谈
  2. wdcp安装多种php版本共存
  3. wordpress如何压缩HTML代码
  4. js base64图片太大_手把手教你常用JS方法封装(一) [ 大杂烩 ]
  5. 用计算机弹飞云直下,飞云之下-韩红/林俊杰-和弦谱-《弹吧》官网tan8.com-和弦谱大全,学吉他,秀吉他...
  6. 1085 Perfect Sequence (25 分)
  7. lstm训练情感分析的优点_「情感分析领域」简单调研
  8. python中给变量赋值时、既确定了变量的值_python中将函数赋值给变量时需要注意的一些问题...
  9. autocad完全应用指南_北京博超时代软件有限公司软件SQL安装指南
  10. 【2019杭电多校第四场1010=HDU6623】Minimal Power of Prime(质因子分解+思维+卡精度)