spring自定义session分布式session
spring实现自定义session、springboot实现自定义session、自定义sessionid的key、value、实现分布式会话
一、原始方案
自定义生成sessionid的值
修改tomcat 的org.apache.catalina.util.HttpServletRequest 包下的生成方法
/**
* Generate and return a new session identifier.
*/
@Override
public String generateSessionId() {
return generateSessionId(jvmRoute);
}
二、使用spring-session框架
maven
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-bom</artifactId>
<version>Corn-SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
自定义生成
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.Session;
import java.time.Duration;
import java.util.Map;
/**
* @Author 绫小路
* @Date 2021/3/10
* @Description 继承 MapSessionRepository 表示将session存储到map中
*/
public class MySessionRepository extends MapSessionRepository {
private Integer defaultMaxInactiveInterval;
public MySessionRepository(Map<String, Session> sessions) {
super(sessions);
}
public void setDefaultMaxInactiveInterval(int defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
@Override
public MapSession createSession() {
//自定义生成id 解码即可看到 byte[] bytes = new BASE64Decoder().decodeBuffer("MTYxNTM1Nzg0OTI2NQ==");
String id = String.valueOf(System.currentTimeMillis());
MapSession result = new MapSession(id);
if (this.defaultMaxInactiveInterval != null) {
result.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
return result;
}
}
配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author 绫小路
* @Date 2021/3/10
*/
@EnableSpringHttpSession
@Configuration
public class MyConfig {
public static Map<String, Session> sessions = new ConcurrentHashMap<>();
@Bean
public SessionRepository mySessionRepository() {
return new MySessionRepository(sessions);
}
@Bean
public CookieSerializer cookieSerializer() {
//默认会将cookie进行 Base64 decode value
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
//允许跨域
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
//cookie 的值不进行base64 编码
serializer.setUseBase64Encoding(false);
return serializer;
}
}
application.properties
server.servlet.session.cookie.name=aa
效果
覆盖 CookieSerializer @Bean
三、通过包装请求会话进行高度自定义
原理是对请求会话进行包装自定义,能够高度支配会话,自由进行自定义开发。例如spring-session原理也是对请求会话进行包装,所以可以通过自定义进行对session的存储,例如存储到内存、redis、数据库、nosql等等。
首选实现HttpSession,并对它进行序列化,其中我添加了自定义id生成
package top.lingkang.testdemo;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import java.io.Serializable;
import java.util.*;
/**
* @author lingkang
* Created by 2022/1/24
*/
public class MyHttpSession implements HttpSession, Serializable {
private static int nextId = 1;
private String id;
private final long creationTime;
private int maxInactiveInterval;
private long lastAccessedTime;
private final ServletContext servletContext;
private final Map<String, Object> attributes;
private boolean invalid;
private boolean isNew;
public MyHttpSession(String id){
this((ServletContext) null);
this.id=id;
}
public MyHttpSession() {
this((ServletContext) null);
}
public MyHttpSession(@Nullable ServletContext servletContext) {
this(servletContext, (String) null);
}
public MyHttpSession(@Nullable ServletContext servletContext, @Nullable String id) {
this.creationTime = System.currentTimeMillis();
this.lastAccessedTime = System.currentTimeMillis();
this.attributes = new LinkedHashMap();
this.invalid = false;
this.isNew = true;
this.servletContext = null;
this.id = id != null ? id : Integer.toString(nextId++);
}
public long getCreationTime() {
this.assertIsValid();
return this.creationTime;
}
public String getId() {
return this.id;
}
public String changeSessionId() {
this.id = Integer.toString(nextId++);
return this.id;
}
public void access() {
this.lastAccessedTime = System.currentTimeMillis();
this.isNew = false;
}
public long getLastAccessedTime() {
this.assertIsValid();
return this.lastAccessedTime;
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void setMaxInactiveInterval(int interval) {
this.maxInactiveInterval = interval;
}
public int getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
public HttpSessionContext getSessionContext() {
throw new UnsupportedOperationException("getSessionContext");
}
public Object getAttribute(String name) {
this.assertIsValid();
Assert.notNull(name, "Attribute name must not be null");
return this.attributes.get(name);
}
public Object getValue(String name) {
return this.getAttribute(name);
}
public Enumeration<String> getAttributeNames() {
this.assertIsValid();
return Collections.enumeration(new LinkedHashSet(this.attributes.keySet()));
}
public String[] getValueNames() {
this.assertIsValid();
return StringUtils.toStringArray(this.attributes.keySet());
}
public void setAttribute(String name, @Nullable Object value) {
this.assertIsValid();
Assert.notNull(name, "Attribute name must not be null");
if (value != null) {
Object oldValue = this.attributes.put(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) oldValue).valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
}
}
} else {
this.removeAttribute(name);
}
}
public void putValue(String name, Object value) {
this.setAttribute(name, value);
}
public void removeAttribute(String name) {
this.assertIsValid();
Assert.notNull(name, "Attribute name must not be null");
Object value = this.attributes.remove(name);
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
}
}
public void removeValue(String name) {
this.removeAttribute(name);
}
public void clearAttributes() {
Iterator it = this.attributes.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
Object value = entry.getValue();
it.remove();
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
}
}
}
public void invalidate() {
this.assertIsValid();
this.invalid = true;
this.clearAttributes();
}
public boolean isInvalid() {
return this.invalid;
}
private void assertIsValid() {
Assert.state(!this.isInvalid(), "The session has already been invalidated");
}
public void setNew(boolean value) {
this.isNew = value;
}
public boolean isNew() {
this.assertIsValid();
return this.isNew;
}
public Serializable serializeState() {
HashMap<String, Serializable> state = new HashMap();
Iterator it = this.attributes.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
Object value = entry.getValue();
it.remove();
if (value instanceof Serializable) {
state.put(name, (Serializable) value);
} else if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
}
}
return state;
}
public void deserializeState(Serializable state) {
Assert.isTrue(state instanceof Map, "Serialized state needs to be of type [java.util.Map]");
this.attributes.putAll((Map) state);
}
}
再创建一个请求包装类MyServletRequestWrapper
package top.lingkang.testdemo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
/**
* @author lingkang
* Created by 2022/1/24
*/
public class MyServletRequestWrapper extends HttpServletRequestWrapper {
private HttpSession session;
public MyServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public HttpSession getSession() {
return session;
}
public void setSession(HttpSession session){
this.session=session;
}
}
最后通过拦截器进行包装类替换,注意,应该经该拦截器放在最前面。
package top.lingkang.testdemo;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author lingkang
* Created by 2022/1/24
*/
@Component
public class ClusterSessionFilter implements Filter {
private Map<String, MyHttpSession> sessionMap = new HashMap<>();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
MyHttpSession myHttpSession = null;
String cookieName = "custom-cookie-name";
// 获取cookie
String cookieValue = getCookieValue(cookieName, request.getCookies());
if (cookieValue != null) {
myHttpSession = sessionMap.get(cookieValue);
}
if (myHttpSession == null) {
// 自定义生成一个唯一id
String id = UUID.randomUUID().toString();
// 生成了id需要添加cookie
HttpServletResponse response = (HttpServletResponse) servletResponse;
Cookie cookie = new Cookie(cookieName, id);
cookie.setPath("/");
response.addCookie(cookie);
myHttpSession = new MyHttpSession(id);
}
// 包装类
MyServletRequestWrapper myServletRequestWrapper = new MyServletRequestWrapper(request);
myServletRequestWrapper.setSession(myHttpSession);
System.out.println(myHttpSession.getId());
filterChain.doFilter(myServletRequestWrapper, servletResponse);
// 将会话存储到内存,也可以选择存储到redis等
sessionMap.put(myServletRequestWrapper.getSession().getId(), (MyHttpSession) myServletRequestWrapper.getSession());
}
private String getCookieValue(String name, Cookie[] cookies) {
if (cookies == null)
return null;
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie.getValue();
}
}
return null;
}
}
需要注意的是,上面的替换方案并没有做session淘汰机制,因为存储在内存中,不做淘汰机制会造成内存溢出
如果将会话存储到redis可以这样,即分布式会话存储方案:
package top.lingkang.testdemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author lingkang
* Created by 2022/1/24
*/
@Component
public class ClusterSessionFilter implements Filter {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
MyHttpSession myHttpSession = null;
String cookieName = "custom-cookie-name";
// 获取cookie
String cookieValue = getCookieValue(cookieName, request.getCookies());
if (cookieValue != null) {
Object o = redisTemplate.opsForValue().get(cookieValue);
if (o != null) {
myHttpSession = (MyHttpSession) o;
}
}
if (myHttpSession == null) {
// 自定义生成一个唯一id
String id = UUID.randomUUID().toString();
// 生成了id需要添加cookie
HttpServletResponse response = (HttpServletResponse) servletResponse;
Cookie cookie = new Cookie(cookieName, id);
cookie.setPath("/");
response.addCookie(cookie);
myHttpSession = new MyHttpSession(id);
}
// 包装类
MyServletRequestWrapper myServletRequestWrapper = new MyServletRequestWrapper(request);
myServletRequestWrapper.setSession(myHttpSession);
filterChain.doFilter(myServletRequestWrapper, servletResponse);
// 将会话存储到内存,也可以选择存储到redis等
redisTemplate.opsForValue().set(myHttpSession.getId(), myServletRequestWrapper.getSession(),1800000, TimeUnit.MILLISECONDS);
}
private String getCookieValue(String name, Cookie[] cookies) {
if (cookies == null)
return null;
for (Cookie cookie : cookies) {
if (name.equals(cookie.getName())) {
return cookie.getValue();
}
}
return null;
}
}
redis的过期机制相当于session淘汰机制,同时又引入了新问题,就是极限情况下的空读问题:get请求要执行3分钟,而session在1分钟后到期,等执行完get再更新会话时发现session被淘汰了。解决方案:获取会话前先预判一下session剩余时间,若session的剩余时间少于5分钟,则直接淘汰这个会话,让用户重新登录。合理的时间分配也很重要,存储在其他地方也要考虑这个极限问题
贴出普通访问:
@RestController
public class WebController {
@Autowired
private HttpServletRequest request;
@GetMapping("")
public Object index() {
request.getSession().setAttribute("a", System.currentTimeMillis());
return "create session";
}
@GetMapping("get")
public Object get() {
HttpSession session = request.getSession();
return session.getAttribute("a");
}
}
后记:通过上面的会话存储可以做分布式集群了,理论上单体应用集群的扩充上限为redis集群的读写上限,假设redis写并发10w/s,那么你的应用集群并发处理能达10w/s。
若对session进一步优化,除去每次更新最后访问,则为读多写少,理论上集群可以无限扩展。
若使用数据库存储可以使用序列化二进制存储。
基于最后一点原理,我开发了分布式会话框架:
https://gitee.com/lingkang_top/final-session
spring自定义session分布式session的更多相关文章
- spring boot 2.x 系列 —— spring boot 实现分布式 session
文章目录 一.项目结构 二.分布式session的配置 2.1 引入依赖 2.2 Redis配置 2.3 启动类上添加@EnableRedisHttpSession 注解开启 spring-sessi ...
- SpringBoot,Security4, redis共享session,分布式SESSION并发控制,同账号只能登录一次
由于集成了spring session ,redis 共享session,导致SpringSecurity单节点的session并发控制失效, springSession 号称 无缝整合httpses ...
- Spring Boot(十一)Redis集成从Docker安装到分布式Session共享
一.简介 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API,Redis也是技术领域使用最为广泛的存储中间件,它是 ...
- 170222、使用Spring Session和Redis解决分布式Session跨域共享问题
使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...
- 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案
分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...
- Spring Session实现分布式session的简单示例
前面有用 tomcat-redis-session-manager来实现分布式session管理,但是它有一定的局限性,主要是跟tomcat绑定太紧了,这里改成用Spring Session来管理分布 ...
- 使用Spring Session和Redis解决分布式Session跨域共享问题
http://blog.csdn.net/xlgen157387/article/details/57406162 使用Spring Session和Redis解决分布式Session跨域共享问题
- Tornado 自定义session,与一致性哈希 ,基于redis 构建分布式 session框架
Tornado 自定义session,与一致性哈希 ,基于redis 构建分布式 session import tornado.ioloop import tornado.web from myhas ...
- Spring Session解决分布式Session问题的实现原理
使用Spring Session和Redis解决分布式Session跨域共享问题 上一篇介绍了如何使用spring Session和Redis解决分布式Session跨域共享问题,介绍了一个简单的案例 ...
- spring boot:使用redis cluster集群作为分布式session(redis 6.0.5/spring boot 2.3.1)
一,为什么要使用分布式session? HpptSession默认使用内存来管理Session,如果将应用横向扩展将会出现Session共享问题, 所以我们在创建web集群时,把session保存到r ...
随机推荐
- Dubbo3应用开发—Dubbo序列化方案(Kryo、FST、FASTJSON2、ProtoBuf序列化方案的介绍和使用)
Dubbo序列化方案(Kryo.FST.FASTJSON2.ProtoBuf序列化方案的介绍和使用) 序列化简介 序列化是Dubbo在RPC中非常重要的一个组成部分,其核心作用就是把网络传输中的数据, ...
- 解密IP分片与重组:数据传输中的关键技术
引言 在上一章节中,我们详细讨论了IP的分类和无分类原则的原理以及其在网络通信中的应用.IP分片与重组是在数据包传输过程中起到关键作用的机制.当数据包的大小超过网络链路的MTU(最大传输单元)限制时, ...
- IntelliJ IDEA安装中文插件
1.运行IntelliJ IDEA程序2.点击左上角"File"//文件3.点击下拉的"Settings" //设置4.点击"Plugins" ...
- 若依vue分离版(ruoyi-vue)跳过token验证,设置白名单
找到SecurityConfig类的configure方法 如图所示 在设置白名单后还需要把接口上的权限标识符去掉.然后需要重启一下项目,热加载不行,会报错.
- 高性能日志脱敏组件:已支持 log4j2 和 logback 插件
项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦. sensitive提供基于注解的方式,并且内置了常见的脱敏方式,便于开发. 同时支持 logb ...
- 如何在云服务上快速拥有洛甲WAF(Web防火墙)
如何在云服务上快速拥有洛甲WAF(Web防火墙) 洛甲WAF是基于openresty的web防火墙,通过配合后台保护您的数据安全,详情参考节点服务器 luojiawaf_lua(nginx+lua) ...
- Keepalived高可用软件概述
Keepalived高可用软件概述: 1)互联网主要的高可用软件:Keepalived.Hearttbeat.其中Keepalived是轻量级的,Keepalived是一款开源.免费的实现网站.数据库 ...
- 【本博客所有关于git文章迭代汇总】git操作(暂存,回退,绑定远程等),看这一篇就够了
1.git常用操作 git 小白操作,无非是clone,然后拉取,提交分支,第一次clone的时候,关联远程分支可能会遇到问题,可以看第四条git关联远程分支 # 在当前目录新建一个Git代码库 $ ...
- Python 数学函数和 math 模块指南
Python 提供了一组内置的数学函数,包括一个广泛的数学模块,可以让您对数字执行数学任务. 内置数学函数.min() 和 max() 函数可用于在可迭代对象中查找最低或最高值: 示例:查找可迭代对象 ...
- Java 7之基础 - 强引用、弱引用、软引用、虚引用(转)
载自:http://blog.csdn.net/mazhimazh/article/details/19752475 1.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对 ...