分布式下的Session解决方案

单点登录在单机模式下是非常好解决的,毕竟只要在单机的Session中存储一份即可,而在分布式下单点登录由于相同服务的机器众多,不可能每个服务器每次都会对同一个服务器服务,所以分布式下的单点登录就成为了一个问题.

单点登录

一般依靠Cookie或者Session实现(Session实际也依靠Cookie,毕竟使用JSESSIONID帮助SESSION进行判断.

解决方案

  1. Session复制

    通过不同服务期间的Session进行同步,进而实现Session的共享

    • 优点:
      • web-server(Tomcat)原生支持,只需要修改配置文件即可
    • 缺点:
      • session同步需要数据传输,占用大量网络带宽,降低了服务器群的业务处理能力
      • 任意一台web-server保存的数据都是所有web-server的session总和,受到内存限制无法水平拓展更多的web-server
      • 大型分布式集群情况下,由于所有web-server都全量保存数据,所以此方案不可取
  2. 客户端存储

    通过存储在用户本地的Cookie中,每次请求携带Cookie中的信息,访问后端程序进行共享

    • 优点:
      • 服务器不需存储session,用户保存自己的session信息到cookie中,节省服务端资源
    • 缺点:
      • 每次http请求,携带用户在cookie中的完整信息,浪费网络带宽
      • session数据放在cookie中,cookie有长度限制4k,不能保存大量信息
      • session数据放在cookie中,存在泄露,篡改,窃取等安全隐患
  3. hash一致性

    通过nginx进行session共享

    • 优点:
      • 只需要改ngain配置,不需要修改应用代码
      • 负载均衡,只要hash属性的值的分布是均匀的,多台web-server的负载时均衡的
      • 可以支持web-server水平扩展(session同步法师不行的,受内存限制)
    • 缺点:
      • session还是存在web-server中的,所以web-server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录
      • 如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确的session
  4. 统一存储

    利用程序员经典思路,外面套一层,再次存在另一个地方

    • 优点:
      • 没有安全隐患
      • 可以水平扩展,数据库/缓存水平切分即可
      • web-server重启或者扩容都不会有session丢失
    • 缺点:
      • 增加了一次网络调用,并且需要修改应用代码,如将所有的getSession方法替换为从Redis查数据的方式.redis获取数据比内存慢很多
  5. jwt登录

  6. Oauth2登录

Spring Session

这里我们采用Redis来实现共享Session,也可以存储在别的位置

根据官网我们这里首先引入依赖

<dependency>
		<groupId>org.springframework.session</groupId>
		<artifactId>spring-session-data-redis</artifactId>
</dependency>

由于是Spring Boot2项目所以我们这里写配置文件

spring.session.store-type=redis

同时也可以配置其他相关信息例如,会话超时时间,会话续期的模式,存储在redis中的key值

当然也可以基于JavaConfig进行配置,这里我们配置了存入Redis的序列化方式

@Bean
  public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
    return new GenericJackson2JsonRedisSerializer();
  }

这里的配置根据源代码中的

@Autowired(required = false)
	@Qualifier("springSessionDefaultRedisSerializer")
	public void setDefaultRedisSerializer(RedisSerializer<Object> defaultRedisSerializer) {
		this.defaultRedisSerializer = defaultRedisSerializer;
	}

进行了序列化方式的配置

这样我们就可以在Controller参数中添加*HttpSession* session即可在多个服务中存储session,当需要session时会去Redis中取出数据

如果需要定义传到前端的Cookie的配置信息

这里还是参考官方文档https://github.com/spring-projects/spring-session/blob/main/spring-session-samples/spring-session-sample-javaconfig-custom-cookie/src/main/java/sample/Config.java

@Bean
	public CookieSerializer cookieSerializer() {
		DefaultCookieSerializer serializer = new DefaultCookieSerializer();
		serializer.setCookieName("JSESSIONID"); // <1>
		serializer.setCookiePath("/"); // <2>
		serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); // <3>
		return serializer;
	}

Spring Session原理

核心类

SessionRepositoryFilter

核心代码
Untitled

这里可以看到它使用这个过滤器单次过滤将每次Session的操作使用

RedisSessionRepository

来进行操作

而过滤操作则使用装饰者模式进行处理,如果写过XSS攻击的过滤器的话肯定会知道这样操作的意义

Untitled 1

这个方法则替换了原有的session,每次获取从redis中获取

Q.E.D.