由于集成了spring session ,redis 共享session,导致SpringSecurity单节点的session并发控制失效,

springSession 号称 无缝整合httpsession,这个应该是没问题的,

但是为什么分布式情况下的session 并发依然是单节点呢?

因为session并发控制是第三方框架的 单节点缓存了session名单.我们要重写框架这一部分代码,把session名单存入到redis.

关于SpringSecruity的Session并发管理,看我另一篇随笔:

SpringBoot整合SpringSecurity,SESSION 并发管理,同账号只允许登录一次

废话说到,这里,看代码:

重写 SessionRegistry

**
* Created by 为 on 2017-6-9.
*/ public class MySessionRegistryImpl implements SessionRegistry, ApplicationListener<SessionDestroyedEvent> { private static final String SESSIONIDS = "sessionIds"; private static final String PRINCIPALS = "principals"; @Resource
private RedisTemplate redisTemplate; protected final Log logger = LogFactory.getLog(SessionRegistryImpl.class);
// private final ConcurrentMap<Object, Set<String>> principals = new ConcurrentHashMap();
// private final Map<String, SessionInformation> sessionIds = new ConcurrentHashMap(); public MySessionRegistryImpl() {
} public List<Object> getAllPrincipals() {
return new ArrayList(this.getPrincipalsKeySet());
} public List<SessionInformation> getAllSessions(Object principal, boolean includeExpiredSessions) {
Set<String> sessionsUsedByPrincipal = this.getPrincipals(((UserDetails)principal).getUsername());
if (sessionsUsedByPrincipal == null) {
return Collections.emptyList();
} else {
List<SessionInformation> list = new ArrayList(sessionsUsedByPrincipal.size());
Iterator var5 = sessionsUsedByPrincipal.iterator(); while (true) {
SessionInformation sessionInformation;
do {
do {
if (!var5.hasNext()) {
return list;
} String sessionId = (String) var5.next();
sessionInformation = this.getSessionInformation(sessionId);
} while (sessionInformation == null);
} while (!includeExpiredSessions && sessionInformation.isExpired()); list.add(sessionInformation);
}
}
} public SessionInformation getSessionInformation(String sessionId) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
return (SessionInformation) this.getSessionInfo(sessionId);
} public void onApplicationEvent(SessionDestroyedEvent event) {
String sessionId = event.getId();
this.removeSessionInformation(sessionId);
} public void refreshLastRequest(String sessionId) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
SessionInformation info = this.getSessionInformation(sessionId);
if (info != null) {
info.refreshLastRequest();
} } public void registerNewSession(String sessionId, Object principal) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
Assert.notNull(principal, "Principal required as per interface contract");
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registering session " + sessionId + ", for principal " + principal);
} if (this.getSessionInformation(sessionId) != null) {
this.removeSessionInformation(sessionId);
} this.addSessionInfo(sessionId, new SessionInformation(principal, sessionId, new Date())); // this.sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
Set<String> sessionsUsedByPrincipal = (Set) this.getPrincipals(principal.toString());
if (sessionsUsedByPrincipal == null) {
sessionsUsedByPrincipal = new CopyOnWriteArraySet();
Set<String> prevSessionsUsedByPrincipal = (Set) this.putIfAbsentPrincipals(principal.toString(), sessionsUsedByPrincipal);
if (prevSessionsUsedByPrincipal != null) {
sessionsUsedByPrincipal = prevSessionsUsedByPrincipal;
}
} ((Set) sessionsUsedByPrincipal).add(sessionId);
this.putPrincipals(principal.toString(), sessionsUsedByPrincipal);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Sessions used by '" + principal + "' : " + sessionsUsedByPrincipal);
} } public void removeSessionInformation(String sessionId) {
Assert.hasText(sessionId, "SessionId required as per interface contract");
SessionInformation info = this.getSessionInformation(sessionId);
if (info != null) {
if (this.logger.isTraceEnabled()) {
this.logger.debug("Removing session " + sessionId + " from set of registered sessions");
} this.removeSessionInfo(sessionId);
Set<String> sessionsUsedByPrincipal = (Set) this.getPrincipals(info.getPrincipal().toString());
if (sessionsUsedByPrincipal != null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Removing session " + sessionId + " from principal's set of registered sessions");
} sessionsUsedByPrincipal.remove(sessionId);
if (sessionsUsedByPrincipal.isEmpty()) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Removing principal " + info.getPrincipal() + " from registry");
} this.removePrincipal(((UserDetails)info.getPrincipal()).getUsername());
} if (this.logger.isTraceEnabled()) {
this.logger.trace("Sessions used by '" + info.getPrincipal() + "' : " + sessionsUsedByPrincipal);
} }
}
} public void addSessionInfo(final String sessionId, final SessionInformation sessionInformation) {
BoundHashOperations<String, String, SessionInformation> hashOperations = redisTemplate.boundHashOps(SESSIONIDS);
hashOperations.put(sessionId, sessionInformation);
} public SessionInformation getSessionInfo(final String sessionId) {
BoundHashOperations<String, String, SessionInformation> hashOperations = redisTemplate.boundHashOps(SESSIONIDS);
return hashOperations.get(sessionId);
} public void removeSessionInfo(final String sessionId) {
BoundHashOperations<String, String, SessionInformation> hashOperations = redisTemplate.boundHashOps(SESSIONIDS);
hashOperations.delete(sessionId);
} public Set<String> putIfAbsentPrincipals(final String key, final Set<String> set) {
BoundHashOperations<String, String, Set<String>> hashOperations = redisTemplate.boundHashOps(PRINCIPALS);
hashOperations.putIfAbsent(key, set);
return hashOperations.get(key);
} public void putPrincipals(final String key, final Set<String> set) {
BoundHashOperations<String, String, Set<String>> hashOperations = redisTemplate.boundHashOps(PRINCIPALS);
hashOperations.put(key,set);
} public Set<String> getPrincipals(final String key) {
BoundHashOperations<String, String, Set<String>> hashOperations = redisTemplate.boundHashOps(PRINCIPALS);
return hashOperations.get(key);
} public Set<String> getPrincipalsKeySet() {
BoundHashOperations<String, String, Set<String>> hashOperations = redisTemplate.boundHashOps(PRINCIPALS);
return hashOperations.keys();
} public void removePrincipal(final String key) {
BoundHashOperations<String, String, Set<String>> hashOperations = redisTemplate.boundHashOps(PRINCIPALS);
hashOperations.delete(key);
} }

重写ConcurrentSessionControlAuthenticationStrategy

/**
* Created by 为 on 2017-6-14.
*/
public class MyConcurrentSessionControlAuthenticationStrategy extends ConcurrentSessionControlAuthenticationStrategy { protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private final SessionRegistry sessionRegistry;
private boolean exceptionIfMaximumExceeded = false;
private int maximumSessions = 1; public MyConcurrentSessionControlAuthenticationStrategy(SessionRegistry sessionRegistry) {
super(sessionRegistry);
Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null");
this.sessionRegistry = sessionRegistry;
} public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
int sessionCount = sessions.size();
int allowedSessions = this.getMaximumSessionsForThisUser(authentication);
if(sessionCount >= allowedSessions) {
if(allowedSessions != -1) {
if(sessionCount == allowedSessions) {
HttpSession session = request.getSession(false);
if(session != null) {
Iterator var8 = sessions.iterator(); while(var8.hasNext()) {
SessionInformation si = (SessionInformation)var8.next();
if(si.getSessionId().equals(session.getId())) {
return;
}
}
}
} this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
}
}
} protected int getMaximumSessionsForThisUser(Authentication authentication) {
return this.maximumSessions;
} protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {
if(!this.exceptionIfMaximumExceeded && sessions != null) {
SessionInformation leastRecentlyUsed = null;
Iterator var5 = sessions.iterator(); while(true) {
SessionInformation session;
do {
if(!var5.hasNext()) {
leastRecentlyUsed.expireNow();
((MySessionRegistryImpl)sessionRegistry).addSessionInfo(leastRecentlyUsed.getSessionId(),leastRecentlyUsed);
return;
} session = (SessionInformation)var5.next();
} while(leastRecentlyUsed != null && !session.getLastRequest().before(leastRecentlyUsed.getLastRequest())); leastRecentlyUsed = session;
}
} else {
throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{Integer.valueOf(allowableSessions)}, "Maximum sessions of {0} for this principal exceeded"));
}
} public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
} public void setMaximumSessions(int maximumSessions) {
Assert.isTrue(maximumSessions != 0, "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
this.maximumSessions = maximumSessions;
} public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "messageSource cannot be null");
this.messages = new MessageSourceAccessor(messageSource);
} }
WebSecurityConfigurerAdapter
   @Bean
public MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter();
myUsernamePasswordAuthenticationFilter.setPostOnly(true);
myUsernamePasswordAuthenticationFilter.setAuthenticationManager(this.authenticationManager());
myUsernamePasswordAuthenticationFilter.setUsernameParameter("name_key");
myUsernamePasswordAuthenticationFilter.setPasswordParameter("pwd_key");
myUsernamePasswordAuthenticationFilter.setVerificationCodeParameter("verification_code");
myUsernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/checkLogin", "POST"));
myUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
myUsernamePasswordAuthenticationFilter.setSessionAuthenticationStrategy(new MyConcurrentSessionControlAuthenticationStrategy(sessionRegistry));
return myUsernamePasswordAuthenticationFilter;
}

开启两个服务,同一个账户登录不同的端口测试,能否被T下线

SpringBoot,Security4, redis共享session,分布式SESSION并发控制,同账号只能登录一次的更多相关文章

  1. SpringBoot系列: Redis 共享Session

    Web项目Session管理是一个很重要的话题, 涉及到系统横向扩展, SpringBoot已经为共享Session很好的解决方案, 这篇文章关注使用Redis共享会话, 同时这也是最常用的方法. = ...

  2. spring+redis+nginx 实现分布式session共享

    1,spring 必须是4.3以上版本的 2,maven配置 添加两个重要的依赖 <dependency> <groupId>org.springframework.sessi ...

  3. SpringBoot使用Redis共享用户session信息

    SpringBoot引入Redis依赖: <dependency> <groupId>org.springframework.boot</groupId> < ...

  4. SpringBoot SpringSession redis 共享 SESSION

    号称无缝整合httpsession 共享, 但注意如果存在第三方框架,例如SESSION并发控制,这个是需要自己重写session名单的. 关于redis session 共享 的session并发控 ...

  5. SpringBoot+Shiro+Redis共享Session入门小栗子

    在单机版的Springboot+Shiro的基础上,这次实现共享Session. 这里没有自己写RedisManager.SessionDAO.用的 crazycake 写的开源插件 pom.xml ...

  6. springboot 整合 redis 共享Session-spring-session-data-redis

    参考:https://www.cnblogs.com/ityouknow/p/5748830.html 如何使用 1.引入 spring-boot-starter-redis <dependen ...

  7. springboot集成redis(mybatis、分布式session)

    安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...

  8. 基于redis解决session分布式一致性问题

    1.session是什么 当用户在前端发起请求时,服务器会为当前用户建立一个session,服务器将sessionId回写给客户端,只要用户浏览器不关闭,再次请求服务器时,将sessionId传给服务 ...

  9. ASP.NET Core中间件实现分布式 Session

    1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件的配置 1.2. 依赖注入中间件 1.3. Cookies ...

随机推荐

  1. Android开发模板代码(一)——简单打开图库选择照片

    首先,先贴上样本代码 //检查权限 public void checkPermission() { if (ContextCompat.checkSelfPermission(this, Manife ...

  2. IIS使用十大原则,(IIS过期时间,IIS缓存设置) 【转载】

    1. 自定义错误页虽然自定义错误页很简单,但只有少数管理员有效地利用了它.管理员可以在MMC中将HTTP错误信息映像到服务器上的绝对URL或是某个文件,更为详细的信息可以在这里找到.如果你嫌这太麻烦, ...

  3. Shader 入门笔记(一)

    本笔记,是根据自己学习shader的笔记,主要是参照冯乐乐的<Shader 入门精要> 和游戏蛮牛shaderLad视频 和网上一些博客. 为啥要学习这个呐? 自己其实之前学过一段时间的s ...

  4. nodejs爬虫笔记(四)---利用nightmare解决加载更多问题

    目标: 解决页面加载更多问题.笔记三中,我们只爬取到网页的部分信息,而点击加载更多后的页面内容是没有提取到的.开始我的想法是找到加载更多的数据接口(可参照:http://www.jianshu.com ...

  5. html集锦

    注意:此内容为复习所总结,非专业,不全,理解记录理解会有偏差. 一.HTML解释: 指的是超文本标记语言 (Hyper Text Markup Language),不是一种编程语言,而是一种标记语言  ...

  6. 基于 React + Webpack 的音乐相册项目(下)

    上一篇我们完成了音乐相册里面的播放图片的功能,这一篇主要完成的是音乐相册里面的音乐播放器功能.最终让我们基于 React 的音乐相册图文并茂.有声有色. 我们主要从以下几个部分来展开: 数据准备 进度 ...

  7. SAP BAPI创建批次 为保存内部对象号

    使用BAPI:BAPI_BATCH_CREATE 创建批次时,能够保存成功,但是MCH1表和KSSK表中没有内部对象号. 无奈,查找源码. 最后在BAPI中调用的这个函数这里找到了问题.此处kzcla ...

  8. Ubuntu14.04 命令行下安装teamviewer

    下载teamviewer 链接:https://pan.baidu.com/s/1hs0BppM  密码:sdmk 上传到 /home/[user] cd /home/[user] 移动安装包到 /o ...

  9. HDU 5996 dingyeye loves stone [阶梯Nim]

    dingyeye喜欢和你玩石子游戏. dingyeye有一棵nn个节点的有根树,节点编号为00到n−1n−1,根为00号节点.游戏开始时,第ii个节点上有a[i]a[i]个石子.两位玩家轮流操作,每次 ...

  10. HDU 3595 GG and MM [Every-SG]

    传送门 题意: 两个数$x,y$,一个人的决策为让大数减去小数的任意倍数(结果不能为负),出现0的人胜 一堆这样的游戏同时玩 Every-SG 游戏规定,对于还没有结束的单一游戏,游戏者必须对该游戏进 ...