疯狂创客圈 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) 聊天程序【 亿级流量】实战 开源项目实战

Java 面试题 一网打尽**


SpringSession 独立使用的更多相关文章

  1. SpringSession header/cookie/attribute存放 session id

    SpringSession header/cookie/attribute存放 SessionID(死磕) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长 ...

  2. SpringBoot SpringCloud 热部署 热加载 热调试

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] Crazy-Sp ...

  3. Eureka 入门,带视频

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  4. springcloud Config 入门,带视频

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  5. spring security 原理+实战

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  6. RedisSession (自定义)

    RedisSession (自定义) 疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Red ...

  7. SpringCloud 脚手架

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  8. SpringCloud 亿级流量 架构演进

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

  9. Zuul 详解,带视频

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 架构师成长+面试必备之 高并发基础书籍 [Netty Zookeeper Redis 高并发实战 ] 前言 Crazy ...

随机推荐

  1. 【Android - 自定义View】之View的draw过程解析

    draw(绘制)过程的作用是将View绘制到屏幕上面.View中有 draw() 方法和 onDraw() 方法,但onDraw()方法是空方法:ViewGroup中没有draw()方法,也没有onD ...

  2. KFC-位置分页爬虫

    import requests import json if name == 'main': #记录总数 raw_count=0 #查询页数 page=1 #存储数据 data=[] #判断有无数据条 ...

  3. Echarts自定义折线图例,增加选中功能

    用Echarts图表开发,原本的Echarts图例不一定能满足我们的视觉要求. 下面是Echarts 折线图自定义图例,图例checked选中,相应的折线线条会随之checked,其余未选中的图例对应 ...

  4. Python爬虫从入门到精通——基本库re的使用:正则表达式【华为云技术分享】

    置顶:华为云618大促火热进行中,全场1折起,免费抽主机,消费满额送P30 Pro,点此抢购. 正则表达式是处理字符串的强大工具,它有自己特定的语法结构,有了它,实现字符串的检索.替换.匹配验证都不在 ...

  5. 基于Docker快速搭建ELK

    [摘要] 本文基于自建的Docker平台速搭建一套完整的ELK系统,相关的镜像直接从Docker Hub上获取,可以快速实现日志的采集和分析检索. 准备镜像 l  获取ES镜像:docker pull ...

  6. es6 proxy浅析

    Proxy 使用proxy,你可以把老虎伪装成猫的外表,这有几个例子,希望能让你感受到proxy的威力. proxy 用来定义自定义的基本操作行为,比如查找.赋值.枚举性.函数调用等. proxy接受 ...

  7. 机器学习-Python 01

    机器学习中最常用最流行的语言工具现阶段应该是Python, 这篇文章主要介绍一些常用的Python语法知识.本篇博文适合那些有其他语言基础的程序员们,如果一点基础都没有,我建议先跳过.博主以前是做移动 ...

  8. ios 10 更新 新体验

    1.使用手机登录开发者网站https://developer.apple.com/download/ 2.下载描述文件 3.安装描述文件,按照提示步骤操作 4.更新ios系统 下面的方法是连接电脑直接 ...

  9. Orleans[NET Core 3.1] 学习笔记(三)( 3 )服务端配置

    服务端配置 Silo通过SiloHostBuilder和许多补充选项类以编程方式进行配置. Silo配置有几个关键方面: Orleans集群信息 集群提供程序(不知道咋翻译) Silo到Silo和Cl ...

  10. 01-使用pipenv管理项目环境

    一.使用pipenv管理项目环境 首先为什么我们不在真实环境下做开发呢?真实环境的一些包被其他服务所依赖,我们安装的包和模块都混杂在一起,这样不便于项目管理,还有可能出现意想不到的错误,于是就出现了包 ...