解决session共享问题
1、session复制(效率低,不使用)
图片解释
2、客户端存储(不安全,不使用)
图片解释
3、hash一致性(网络变导致ip变,不使用)
图片解释
4、统一存储(好用易配置,使用)
1)图片解释
2)具体配置
①加入spring session整合redis的依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
该依赖来自于:1. Updating Dependencies
如果没有加入redis的依赖请加入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!--使用Jmeter进行压测的时候会出现:
Redis exception; nested exception is io.lettuce.core.RedisException: io.netty.util.internal.OutOfDirectMemoryError
说明内存超了,这是因为连接springboot中连接redis用的是lettuce-core,而它底层用netty通信,而netty不会及时关闭连接,
所以就会出现内存溢出问题
-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--2、和上面redis依赖连用,用来排除lettuce-core,然后使用jedis连接redis,
具体解释可以看:https://blog.csdn.net/SearchB/article/details/110470094-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
②在application.properties中配置如下代码:
spring.session.store-type=redis
要看解释就看:2. Spring Boot Configuration
你还可以配置超时时间等其他配置,具体配置方式还是看上面的2. Spring Boot Configuration
,例如:
spring.session.timeout: 30m
当然如果你不配置的话,session的过期时间就是30分钟
当然你还需要配置redis的信息,可以根据3. Configuring the Redis Connection来配置
③在启动类上添加@EnableRedisHttpSession
注解,来自于:Spring Java Configuration
④配置cookie名称和cookie的domain(即范围),以及配置使用json的序列化机制
首先说一下cookie名称和范围是什么,我用谷歌浏览器举个例子:
然后我们来说一下它们的作用,首先名称就是一个指示作用,一般用我们自己的
然后说范围有什么用?这涉及域名问题,domain默认的值就是当前最多级别的域名,例如美团北京站是https://bj.meituan.com
,那么默认domain就是bj.meituan.com
,当你登录了美团北京站之后,假设你的信息保存在服务端的session中,这个时候浏览器中对应服务端session信息的cookie令牌中domain就是bj.meituan.com
,如果你把当前美团站点切换成了上海站(url:https://sh.meituan.com
),那么你会发现cookie令牌消失了,因此当前站点的默认domain是sh.meituan.com
,这是因为美团北京站和美团上海站的默认domain不一样,所以cookie会消失,所以为了避免这种情况的出现,所以我们需要在服务端设置domain,例如我们将domain设置成了meituan.com
,这样无论是美团北京站还是美团上海站都可以使用这个domain,因此浏览器中对应服务端session信息的cookie令牌就不会消失了
然后我们还需要将默认jdk序列化机制变成json序列化机制,这样做有两个原因,其一是默认使用jdk序列化机制需要让实体类实现Serializable接口,比较麻烦;其二当我们使用redis客户端工具去查看redis的时候发现jdk序列化后的内容无法识别,基于以上两点,因此我们需要将序列化机制变成json序列化机制
如果要实现以上两点,我们可以创建一个SessionConfig配置类,当然这个类名是随便起的,相应的解释上面也有具体的地址,具体做法如下:
@Configuration
public class SessionConfig {
/**
* 以下配置来自:https://docs.spring.io/spring-session/docs/2.1.13.RELEASE/reference/html5/guides/java-custom-cookie.html#custom-cookie-spring-configuration
* 配置cookie名称
* 配置cookie存储的路径,相同的二级域名下都会有这个cookie
* @return
*/
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("GULISESSION");
serializer.setDomainName("gulimall.com");
return serializer;
}
/**
* 序列化机制使用json,而不用默认的jdk序列化机制,来自:https://github.com/spring-projects/spring-session/blob/2.1.13.RELEASE/samples/boot/redis-json/src/main/java/sample/config/SessionConfig.java
* @return
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
以上内容来自spring官方网站,我带大家看一下如何找到上面链接中的配置内容;
首先我们找到spring session文档信息,如下:
进入文档之后先看HttpSession with Redis Guide
,这里面有spring session整合redis的注解、spring session在application.properties中的配置信息、redis的配置信息等,然后在看点击5.2 HttpSession with Redis
,这个里面主要介绍springsession在启动类上的注解是什么,如下:
然后我们来看配置文件中配置的json序列化配置来自哪里,如下:
然后我们来看如何配置cookie的名称、范围等等信息,如下:
3)spring session的原理
先从启动类上的@EnableRedisHttpSession
注解看,按着Ctrl点进去,你会发现:
我们进入RedisHttpSessionConfiguration
类,首先看里面的第一个大方法:
@Bean
public RedisOperationsSessionRepository sessionRepository()
我们按着Ctrl点RedisOperationsSessionRepository
,然后我们看一下该类的结构,如下:
可以看出里面全部都是在Redis中对Session进行增删改查的操作,我们回到RedisHttpSessionConfiguration
类,可以发现该类继承了SpringHttpSessionConfiguration
类,我们进入SpringHttpSessionConfiguration
类,发现里面有一个过滤器方法:
@Bean
public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter
我们进入SessionRepositoryFilter
类,在该类的构造方法中可以看到该类的构造器,如下:
public SessionRepositoryFilter(SessionRepository<S> sessionRepository) {
if (sessionRepository == null) {
throw new IllegalArgumentException("sessionRepository cannot be null");
}
this.sessionRepository = sessionRepository;
}
里面有一行代码是:this.sessionRepository = sessionRepository;
,我们找到该属性:
private final SessionRepository<S> sessionRepository;
如果你进入SessionRepository接口
,你可以发现它的实现类有这么几个:
其中RedisOperationsSessionRepository
类就是上面提到的在redis中进行session增删改查的那个类,我们回到SessionRepositoryFilter
类中的构造方法,根据上面的分析,其实this.sessionRepository = sessionRepository
这行代码中的sessionRepository对象
对应的类就是RedisOperationsSessionRepository
类,然后SessionRepositoryFilter
类中还有一个doFilterInternal方法
,如下:
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)
我们来分析一下该方法:
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
finally {
wrappedRequest.commitSession();
}
}
先来看request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository)
,其中该常量是public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class .getName()
,这就是session库的名称,现在来看这个库使用的就是RedisOperationsSessionRepository
类,上面我们已经说过了,这个类用于在redis中进行session增删改查,然后看filterChain.doFilter(wrappedRequest, wrappedResponse)
,过滤器放行的时候放行的不是我们普通的request
和response
,而是经过处理之后的wrappedRequest
和wrappedResponse
,其中的一个处理就是对session的处理,之前我们使用的session库是将session储存到内容中,现在使用的是RedisOperationsSessionRepository
类来在redis中增删改查session信息,我们使用session的时候是这样做的:
@GetMapping("/oauth2.0/weibo/success")
public String weiboLogin(@RequestParam("code") String code, HttpSession session){
………………………………
// 将用户信息存储在session中
session.setAttribute(SessionConstant.LOGIN_USER, entityVo);
………………………………
}
其中HttpSession session
相当于HttpSession session = request.getSession()
,此时的HttpServletRequest request
其实就是我们上面提到的SessionRepositoryRequestWrapper wrappedRequest
,正是这个原因,我们才能把改变默认的session库
,而是使用RedisOperationsSessionRepository
类去操作redis对session进行增删改差
最后补充一点session自动续期
:虽然我们设置了session在redis中的过期时间,但是只要我们在浏览器页面上和服务器进行交互,spring session就会给redis中存储的session自动续期,虽然不是直接续满,但是会添加点过期时间,这是很棒的操作,非常人性化,毕竟谁也不想用会网站就要登录一次,那太恶心了
4)单点登录
当我们在微博官网(https://www.sina.com.cn
)登录之后,你会发现我们在新浪官网(https://www.sina.com.cn
)也会自动登录,这里面使用的就是单点登录,使用spring session仅仅可以解决相同二级域名下的session共享问题,但是无法解决这种甚至顶级域名都不同的情况,这种情况就需要我们进行自己写代码处理了,在谷粒商城项目中老师提供了部分思路,可以帮助我们理解单点登录是如何实现的,视频地址是:https://www.bilibili.com/video/BV1np4y1C7Yf,可以看第233集
到235集
,里面没有什么复杂的操作,主要就是在url地址后面加上资源地址参数、加上token参数、在ssoserver.com中的cookie中存储token、在隐藏域中放置资源地址参数、将用户信息通过ssoserver.com存储在redis中、从redis中取出用户信息、对是否带有token进行判断、对cookie中是否带有token进行判断、对session中是否有用户信息进行判断等,我也绘制了一个时序图,用的是Rational Rose
,点我下载,提取码是:3ba1