spring security 开启会话管理,使用redis共享session,开启防御固话攻击后,redis索引和sessions中的sessionId不一致,导致session并发不起作用
也无法获取到用户的所有会话
sessionRegistry.getAllSessions(SecurityContextHolder.getContext().getAuthentication().getPrincipal())
配置:
RedisSessionConfig
public RedisSessionConfig(ObjectProvider<RedisConnectionFactory> redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory.getIfAvailable();
}
@Bean
public RedisOperations<Object, Object> sessionRedisOperations() {
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
RedisTemplate<Object, Object> objectRedisTemplate = new RedisTemplate<>();
objectRedisTemplate.setConnectionFactory(this.redisConnectionFactory);
objectRedisTemplate.setKeySerializer(stringRedisSerializer);
objectRedisTemplate.setHashKeySerializer(stringRedisSerializer);
objectRedisTemplate.afterPropertiesSet();
this.redisTemplate = objectRedisTemplate;
return this.redisTemplate;
}
@Bean("redisSessionRepository")
public FindByIndexNameSessionRepository redisSessionRepository(RedisOperations<Object, Object> sessionRedisOperations) {
RedisIndexedSessionRepository redisIndexedSessionRepository = new RedisIndexedSessionRepository(sessionRedisOperations);
this.sessionRepository = redisIndexedSessionRepository;
return redisIndexedSessionRepository;
}
@Bean
@DependsOn("redisSessionRepository")
public SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry(sessionRepository);
}
WebSecurityConfigurerAdapter:
@Autowired
private SpringSessionBackedSessionRegistry sessionRegistry;
http.sessionManagement()
.maximumSessions(1)//限定一个
.maxSessionsPreventsLogin(false)//踢除前一个用户会话
.sessionRegistry(sessionRegistry)
调试断点:
防御固话攻击 使用 changeSessionId(用户身份验证后不会创建新会话,但会更改会话 ID。)
使用 FindByIndexNameSessionRepository 操作session,存储到redis
当sessionId变化时调用RedisSession.save()
private void save() {
this.saveChangeSessionId();
this.saveDelta();
}
private void saveDelta() {
if (!this.delta.isEmpty()) {
String sessionId = this.getId();
RedisIndexedSessionRepository.this.getSessionBoundHashOperations(sessionId).putAll(this.delta);
String principalSessionKey = RedisIndexedSessionRepository.getSessionAttrNameKey(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
String securityPrincipalSessionKey = RedisIndexedSessionRepository.getSessionAttrNameKey("SPRING_SECURITY_CONTEXT");
if (this.delta.containsKey(principalSessionKey) || this.delta.containsKey(securityPrincipalSessionKey)) {
if (this.originalPrincipalName != null) {
String originalPrincipalRedisKey = RedisIndexedSessionRepository.this.getPrincipalKey(this.originalPrincipalName);
RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(originalPrincipalRedisKey).remove(new Object[]{sessionId});
}
Map<String, String> indexes = RedisIndexedSessionRepository.this.indexResolver.resolveIndexesFor(this);
String principal = (String)indexes.get(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
this.originalPrincipalName = principal;
if (principal != null) {
String principalRedisKey = RedisIndexedSessionRepository.this.getPrincipalKey(principal);
RedisIndexedSessionRepository.this.sessionRedisOperations.boundSetOps(principalRedisKey).add(new Object[]{sessionId});
}
}
this.delta = new HashMap(this.delta.size());
Long originalExpiration = this.originalLastAccessTime != null ? this.originalLastAccessTime.plus(this.getMaxInactiveInterval()).toEpochMilli() : null;
RedisIndexedSessionRepository.this.expirationPolicy.onExpirationUpdated(originalExpiration, this);
}
}
这里就会出现问题,只更新了sessions里面的 ,没有更新索引,会话管理又是根据索引找session,所以导致会话管理不起效
问题找到了,但是没有找到解决办法,各位有什么解决办法吗?万分感谢
2021年6月4日 补充:
放到测试环境,会话管理就正常了。
本地调试就是不起作用。