SpringSession 独立使用
疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口 】
架构师成长+面试必备之 高并发基础书籍 【Netty Zookeeper Redis 高并发实战 】
前言
Crazy-SpringCloud 微服务脚手架 &视频介绍:
Crazy-SpringCloud 微服务脚手架,是为 Java 微服务开发 入门者 准备的 学习和开发脚手架。并配有一系列的使用教程和视频,大致如下:
高并发 环境搭建 图文教程和演示视频,陆续上线:
中间件 | 链接地址 |
---|---|
Linux Redis 安装(带视频) | Linux Redis 安装(带视频) |
Linux Zookeeper 安装(带视频) | Linux Zookeeper 安装, 带视频 |
Windows Redis 安装(带视频) | Windows Redis 安装(带视频) |
RabbitMQ 离线安装(带视频) | RabbitMQ 离线安装(带视频) |
ElasticSearch 安装, 带视频 | ElasticSearch 安装, 带视频 |
Nacos 安装(带视频) | Nacos 安装(带视频) |
Crazy-SpringCloud 微服务脚手架 图文教程和演示视频,陆续上线:
组件 | 链接地址 |
---|---|
Eureka | Eureka 入门,带视频 |
SpringCloud Config | springcloud Config 入门,带视频 |
spring security | spring security 原理+实战 |
Spring Session | SpringSession 独立使用 |
分布式 session 基础 | RedisSession (自定义) |
重点: springcloud 开发脚手架 | springcloud 开发脚手架 |
SpingSecurity + SpringSession 死磕 (写作中) | SpingSecurity + SpringSession 死磕 |
小视频以及所需工具的百度网盘链接,请参见 疯狂创客圈 高并发社群 博客
SpringSession 独立使用 的场景和问题
当Zuul网关接收到http请求后,当请求进入对应的Filter进行过滤,通过 SpringSecurity 认证后,提取 SessionID,转发给各个微服务,通过Spring-Session创建的分布式微服务,实现Session共享!
特点:
(1)浏览器和移动端,和Nginx代理,token 是可见的,但是 session 不可见。
(2)各个微服务,用到共享Session,sessionId是可见的。
(3)各个微服务,可以通过自定义的 SessionHolder 共享类,可以静态的取得分布式Session的公共数据,比如基础的用户信息。提升编程的效率。 具体请参见 SpringCloud 开发脚手架。
具体场景的请求处理流程:
问题:
问题一:需要定制ID解析器
场景1 :如果Rest请求从Zuul 过来,Zuul 会在头部设置 sessionID,就是这个场景首先从head中去取
String headerValue = request.getHeader(this.headerName);
场景2: 如果是 单体微服务直接访问 ,就是这个场景 SpringSecurity 会将 sessionID,放在 attribute中。这种场景,直接从从attribute中去取sessionID
headerValue = (String) request.getAttribute(SessionConstants.SESSION_SEED);
SpringSession自带的 ID解析器 ,不能满足要求,需要重新定制一个。关于ID解析器,请参见 疯狂创客圈 的另一博文 SpringSession自带的 ID解析器 最全解读
问题二:需要定制sessionRepository 存储器
sessionRepository 负责存储 session 到Redis,需要修改模式为立即提交,以免setAttribute的属性,不能及时写入Redis,这是笔者调试了几个小时发现的坑
问题三:需要定制SessionRepositoryFilter 过滤器
将Session请求,保持到 SessionHolder 的 ThreadLocal 本地变量中,方便统一获取,方便编程。例如:
SessionHolder.getSessionUser().getLoginName());
直接从redissession,读取用户的名称,多方便呀。
总之: 使用集成的默认的SpringSession ,没有办法深入的解决问题。 有两种方法。
- 第一种是自制 分布式 Session。
具体请参考 疯狂创客圈 博客 分布式RedisSession 自制
这种方法的优点:简陋。 缺点:过于简陋。
在流程和思想上,和第下面的第二种是类似的,可供学习使用,方便理解。
- 第二种是 SpringSession 独立使用。
就是本文的内容。
说明: 第二种在流程和思想上第一种是类似的,可供学习使用,方便理解,建议先了解第一种,第二种就好掌握多了。
理论基础: springSession 原理
spring-session分为以下核心模块:
过滤器 SessionRepositoryFilter:Servlet规范中Filter的实现,用 Spring Session 替换原来的 HttpSession,具体的方式是使用了自己的两个包装器: HttpServletRequest 和HttpServletResponse。
包装器 HttpServerletRequest/HttpServletResponse/HttpSessionWrapper:包装原有的HttpServletRequest、HttpServletResponse和Spring Session,实现切换Session和透明继承HttpSession的关键之所在
Session:Spring Session模块
存储器 SessionRepository:负责 Spring Session的存储
具体见下图:
Spring Session模块
spring-session中则抽象出单独的Session层接口,让后再使用适配器模式将Session适配层Servlet规范中的HttpSession。
类图如下:
RedisSession 的本质:
内部封装一个 MapSession,MapSession 本质是一个 map。而 RedisSession 的主要职责:负责 MapSession中 Map 的K-V内容的 Redis 存储。
spring-session 原理,请参见博文
第1步: ID解析器 自定义
场景1 :如果Rest请求从Zuul 过来,Zuul 会在头部设置 sessionID,就是这个场景首先从head中去取
String headerValue = request.getHeader(this.headerName);
场景2: 如果是 单体微服务直接访问 ,就是这个场景 SpringSecurity 会将 sessionID,放在 attribute中。这种场景,直接从从attribute中去取sessionID
headerValue = (String) request.getAttribute(SessionConstants.SESSION_SEED);
实现 HttpSessionIdResolver 接口,定义一个完整的ID解析器,代码如下:
package com.crazymaker.springcloud.standard.config;
//...省略import
@Data
public class CustomedSessionIdResolver implements HttpSessionIdResolver {
private RedisTemplate<Object, Object> redisTemplet = null;
private static final String HEADER_AUTHENTICATION_INFO = "Authentication-Info";
private final String headerName;
/**
* The name of the header to obtain the session id from.
*
*/
public CustomedSessionIdResolver() {
//设置 head头的名称
this.headerName = SessionConstants.SESSION_SEED;
if (headerName == null) {
throw new IllegalArgumentException("headerName cannot be null");
}
}
@Override
public List<String> resolveSessionIds(HttpServletRequest request) {
//step1:首先从head中去取sessionID
// 如果从Zuul 过来,就是这个场景
String headerValue = request.getHeader(this.headerName);
//step1:首先从attribute中去取sessionID
// 如果是 单体微服务直接访问 ,就是这个场景
//SpringSecurity 会将 sessionID,放在 attribute中
if (StringUtils.isEmpty(headerValue)) {
headerValue = (String) request.getAttribute(SessionConstants.SESSION_SEED);
if (!StringUtils.isEmpty(headerValue)) {
headerValue = SessionConstants.getRedisSessionID(headerValue);
}
}
return (headerValue != null) ?
Collections.singletonList(headerValue) : Collections.emptyList();
}
@Override
public void setSessionId(HttpServletRequest request, HttpServletResponse response,
String sessionId) {
//不需要返回sessionId
//到前端
response.setHeader(this.headerName, "");
// response.setHeader(this.headerName, sessionId);
}
@Override
public void expireSession(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(this.headerName, "");
}
//....省略其他
}
第2步:自定义一个SessionRepositoryFilter
这一步,不是必须的。
主要作用: 在过滤器的处理方法 doFilterInternal(....), 要将 redis session 保存到 SessionHolder 类中,方便后面访问。代码如下:
SessionHolder.setRequest(wrappedRequest);
SessionHolder.setSession(wrappedRequest.getSession());
复制源码中的 SessionRepositoryFilter 类,改名为 CustomedSessionRepositoryFilter, 简单的修改一下,代码如下:
package com.crazymaker.springcloud.standard.security.filter;
//.....
public class CustomedSessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
private static final String SESSION_LOGGER_NAME = CustomedSessionRepositoryFilter.class
.getName().concat(".SESSION_LOGGER");
//....
//默认的ID解析器,需要替换掉
private HttpSessionIdResolver httpSessionIdResolver = new CookieHttpSessionIdResolver();
/**
* Creates a new instance.
*
* @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
*/
public CustomedSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
if (sessionRepository == null) {
throw new IllegalArgumentException("sessionRepository cannot be null");
}
this.sessionRepository = sessionRepository;
}
/**
* Sets the {@link HttpSessionIdResolver} to be used. The default is a
* {@link CookieHttpSessionIdResolver}.
*
* @param httpSessionIdResolver the {@link HttpSessionIdResolver} to use. Cannot be
* null.
*/
public void setHttpSessionIdResolver(HttpSessionIdResolver httpSessionIdResolver) {
if (httpSessionIdResolver == null) {
throw new IllegalArgumentException("httpSessionIdResolver cannot be null");
}
this.httpSessionIdResolver = httpSessionIdResolver;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
if(this.servletContext==null)
{
this.servletContext=request.getServletContext();
}
SessionRepositoryRequestWrapper wrappedRequest =
new SessionRepositoryRequestWrapper(request, response, this.servletContext);
SessionRepositoryResponseWrapper wrappedResponse =
new SessionRepositoryResponseWrapper(wrappedRequest, response);
/**
* 将Session请求,保持到 SessionHolder 的 ThreadLocal 本地变量中,方便统一获取
*/
SessionHolder.setRequest(wrappedRequest);
SessionHolder.setSession(wrappedRequest.getSession());
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
wrappedRequest.commitSession();
}
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* Allows ensuring that the session is saved if the response is committed.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryResponseWrapper
extends OnCommittedResponseWrapper {
//.....
}
/**
* A {@link javax.servlet.http.HttpServletRequest} that retrieves the
* {@link javax.servlet.http.HttpSession} using a
* {@link org.springframework.session.SessionRepository}.
*
* @author Rob Winch
* @since 1.0
*/
private final class SessionRepositoryRequestWrapper
extends HttpServletRequestWrapper {
//....
}
static class HttpSessionAdapter<S extends Session> implements HttpSession {
//....
}
}
第3步:自动配置 Configuration 的定制
简单粗暴,将springsession 默认的自动配置,废掉了。
复制一份 RedisHttpSessionConfiguration, 名字叫做 CustomedRedisHttpSessionConfiguration ,主要作用:
(1) 创建 CustomedSessionIdResolver ID解析器的IOC Bean
(2) 创建 sessionRepository 保存器 的IOC Bean时,修改模式为立即提交
package com.crazymaker.springcloud.standard.config;
//....
@Configuration
@EnableScheduling
public class CustomedRedisHttpSessionConfiguration
implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware,
SchedulingConfigurer {
static final String DEFAULT_CLEANUP_CRON = "0 * * * * *";
//......
@DependsOn("httpSessionIdResolver")
@Bean
public RedisOperationsSessionRepository sessionRepository(CustomedSessionIdResolver httpSessionIdResolver) {
RedisTemplate<Object, Object> redisTemplate = createRedisTemplate();
RedisOperationsSessionRepository sessionRepository =
new RedisOperationsSessionRepository(redisTemplate);
sessionRepository.setApplicationEventPublisher(this.applicationEventPublisher);
if (this.defaultRedisSerializer != null) {
sessionRepository.setDefaultSerializer(this.defaultRedisSerializer);
}
sessionRepository
.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
if (StringUtils.hasText(this.redisNamespace)) {
sessionRepository.setRedisKeyNamespace(this.redisNamespace+":"+SessionConstants.REDIS_SESSION_KEY_PREFIX);
}
//修改模式为立即提交
sessionRepository.setRedisFlushMode(RedisFlushMode.IMMEDIATE);
// sessionRepository.setRedisFlushMode(this.redisFlushMode);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
httpSessionIdResolver.setRedisTemplet(redisTemplate);
this.sessionRepository = sessionRepository;
return sessionRepository;
}
//....
/**
* 配置 ID 解析器,从 header 解析id
*
* @return
*/
@Bean("httpSessionIdResolver")
public CustomedSessionIdResolver httpSessionIdResolver() {
return new CustomedSessionIdResolver(SessionConstants.SESSION_ID);
}
}
第4步: 在SpringSecurityConfig中,使用过滤器
package com.crazymaker.springcloud.user.info.config;
//....
import javax.annotation.Resource;
import java.util.Arrays;
@EnableWebSecurity()
public class UserProviderWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserLoginService userLoginService;
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html",
"/api/user/login/v1",
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin().disable()
.sessionManagement().disable()
.cors()
.and()
.addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
.apply(new JsonLoginConfigurer<>()).loginSuccessHandler(jsonLoginSuccessHandler())
.and()
.apply(new JwtLoginConfigurer<>()).tokenValidSuccessHandler(jwtRefreshSuccessHandler()).permissiveRequestUrls("/logout")
.and()
.logout()
.addLogoutHandler(tokenClearLogoutHandler())
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.and()
.addFilterBefore(springSessionRepositoryFilter(), SessionManagementFilter.class)
.sessionManagement().disable()
;
}
@Resource
RedisOperationsSessionRepository sessionRepository;
@Resource
public CustomedSessionIdResolver httpSessionIdResolver;
@DependsOn({"sessionRepository","httpSessionIdResolver"})
@Bean("jwtAuthenticationProvider")
protected AuthenticationProvider jwtAuthenticationProvider() {
return new JwtAuthenticationProvider(sessionRepository,httpSessionIdResolver);
}
//....
}
具体,请关注 Java 高并发研习社群 【博客园 总入口 】
最后,介绍一下疯狂创客圈:疯狂创客圈,一个Java 高并发研习社群 【博客园 总入口 】
疯狂创客圈,倾力推出:面试必备 + 面试必备 + 面试必备 的基础原理+实战 书籍 《Netty Zookeeper Redis 高并发实战》
疯狂创客圈 Java 死磕系列
- Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战
- Netty 源码、原理、JAVA NIO 原理
- Java 面试题 一网打尽
- 疯狂创客圈 【 博客园 总入口 】
Java 面试题 一网打尽**
- 疯狂创客圈 【 博客园 总入口 】
SpringSession 独立使用的更多相关文章
- SpringSession header/cookie/attribute存放 session id
SpringSession header/cookie/attribute存放 SessionID(死磕) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长 ...
- SpringBoot SpringCloud 热部署 热加载 热调试
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] Crazy-Sp ...
- Eureka 入门,带视频
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...
- springcloud Config 入门,带视频
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...
- spring security 原理+实战
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...
- RedisSession (自定义)
RedisSession (自定义) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Red ...
- SpringCloud 脚手架
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...
- SpringCloud 亿级流量 架构演进
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...
- Zuul 详解,带视频
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...
随机推荐
- java 静态变量&静态方法
1. 静态变量是static修饰的成员变量(类变量),若无static修饰,则是实例变量.静态变量是一种全局变量,它属于某个类,不属于某个对象实例,是在各对象实例间共存. 访问静态变量直接通过类名 ...
- 宝塔面板的数据库连接本地Navicat Premium
把宝塔面板的数据库连接本地Navicat Premium,这样就方便操作了.下面我教大家如何实现. 第一步:在阿里云的安全组添加安全规则,配置安全组放开3306端口 第二步:打开宝塔设置数据库的权限, ...
- 在ubuntu18.04中安装opencv_contrib-3.2.0采坑教程
由于最近要在OpenCV3中使用SIFT和SURF特征提取,而自从OpenCV2升级到OpenCV3版本后,SIFT.SURF等这些算法都被移出opencv默认项目库,而被放到叫opencv_cont ...
- 使用Spring Data JPA的Specification构建数据库查询
Spring Data JPA最为优秀的特性就是可以通过自定义方法名称生成查询来轻松创建查询SQL.Spring Data JPA提供了一个Repository编程模型,最简单的方式就是通过扩展Jpa ...
- FF.PyAdmin 接口服务/后台管理微框架 (Flask+LayUI)
源码(有兴趣的朋友请Star一下) github: https://github.com/fufuok/FF.PyAdmin gitee: https://gitee.com/fufuok/FF.Py ...
- 使用正则表达式实现(加减乘除)计算器(C#实现)
起因:公司领导要求做一款基于行业规范的计算器, 然后需要用户输入一些数据,然后根据用户输入的数据满足某些条件后,再根据用户输入的条件二进行加减乘除运算.;-) 期间因为查找规范等形成数据表的某一列是带 ...
- linux mysql,tomcat与java的安装
先将服务器的安全组设置为 把所有端口或者所需要的端口开放 然后测试远程访问 ssh 用户@ip 登录 输入密码 service iptables stop ...
- 详解numpy的argmax
从最简单的例子出发 假定现在有一个数组a = [3, 1, 2, 4, 6, 1]现在要算数组a中最大数的索引是多少.这个问题对于刚学编程的同学就能解决.最直接的思路,先假定第0个数最大,然后拿这个和 ...
- Python3 并发编程3
目录 GIL全局解释器锁 基本概念 多线程的作用 死锁现象 递归锁 信号量 线程队列 GIL全局解释器锁 基本概念 global interpreter lock 全局解释器锁 GIL不是Python ...
- 【Nodejs】326- 从零开发一个node命令行工具
本文由 IMWeb 社区授权转载自腾讯内部 KM 论坛.点击阅读原文查看 IMWeb 社区更多精彩文章. 什么是命令行工具? 命令行工具(Cmmand Line Interface)简称cli,顾名思 ...