疯狂创客圈 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. centos7忘记root密码的重置方法-超简单

    忘记root密码,重置root密码8步. 1.在开机界面按e进入grub编辑模式 2.找到 ro修改为rw /sysroot/bin/sh 3.Ctrl + x 启动 4.chroot /sysroo ...

  2. CSS 了解一下

    CSS 认识一下 1.CSS 的那些事 CSS(Cascading Style Sheets)译「层叠样式表」,我的理解是:各种样式叠加的表. 一个网页,如果没有 CSS,就是穿着"国王的新 ...

  3. Mysql多表关系

    mysql多表关系 多表关系是关系型数据库特有的 三种关系 一对一关系 一对多关系 多对多关系 总结 一对一 例子:用户和用户信息 外键设置在用户上,外键字段唯一非空 添加 无级联:先增加被关联表记录 ...

  4. PHP的array_walk和array_map函数实现数组值UTF-8转GBK编码

    在PHP中,array_walk() 和 array_map()两个函数都可以实现对数组中每个值的修改,比如本例就是将数组中所有的值,由UTF-8编码转成GBK编码. 当然,除了这两个函数,也可以用 ...

  5. 页面嵌套iframe的时候引发的js交互问题

    今天在做一个新页面的时候,用到了iframe这个东西.结果出现了一个有趣的问题.自己写的页面和iframe里边的页面属性和js有冲突.具体的点说就是层级出现了问题.不能正常显示.不管怎么修改,总是解决 ...

  6. universal link使用

    iOS9之后,苹果推出了universal link方案,该方案较url scheme有明显的改善.url scheme很难做到唯一. 而 universal link却是你自己控制的. 1.有一个H ...

  7. 揭秘 iOS App Extension 开发 —— Today 篇

    转自:http://www.cocoachina.com/ios/20160619/16760.html 本文授权转载,作者:Cyandev(简书) 从 iOS 8 开始,苹果引入了全新的 App E ...

  8. Node升级——Node Binary管理模块"n"

    Node.js的开发非常活跃,它的最新稳定版本也频繁变化,你不时会发现,一个模块不能在你当前的Node版本上使用,此时你需要升级Node 幸运的是,可以用一种非常简单的方法来管理你的Node版本,即使 ...

  9. ASE19团队项目beta阶段Backend组 scrum6 记录

    本次会议于12月12日,19:30在微软北京西二号楼sky garden召开,持续10分钟. 与会人员:Zhikai Chen, Lihao Ran, Xin Kang 请假人员:Hao Wang 每 ...

  10. (全国多校重现赛一) H Numbers

    zk has n numbers a1,a2,...,ana1,a2,...,an. For each (i,j) satisfying 1≤i<j≤n, zk generates a new ...