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的更多相关文章

  1. spring boot 2.x 系列 —— spring boot 实现分布式 session

    文章目录 一.项目结构 二.分布式session的配置 2.1 引入依赖 2.2 Redis配置 2.3 启动类上添加@EnableRedisHttpSession 注解开启 spring-sessi ...

  2. SpringBoot,Security4, redis共享session,分布式SESSION并发控制,同账号只能登录一次

    由于集成了spring session ,redis 共享session,导致SpringSecurity单节点的session并发控制失效, springSession 号称 无缝整合httpses ...

  3. Spring Boot(十一)Redis集成从Docker安装到分布式Session共享

    一.简介 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API,Redis也是技术领域使用最为广泛的存储中间件,它是 ...

  4. 170222、使用Spring Session和Redis解决分布式Session跨域共享问题

    使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...

  5. 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案

    分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...

  6. Spring Session实现分布式session的简单示例

    前面有用 tomcat-redis-session-manager来实现分布式session管理,但是它有一定的局限性,主要是跟tomcat绑定太紧了,这里改成用Spring Session来管理分布 ...

  7. 使用Spring Session和Redis解决分布式Session跨域共享问题

    http://blog.csdn.net/xlgen157387/article/details/57406162 使用Spring Session和Redis解决分布式Session跨域共享问题

  8. Tornado 自定义session,与一致性哈希 ,基于redis 构建分布式 session框架

    Tornado 自定义session,与一致性哈希 ,基于redis 构建分布式 session import tornado.ioloop import tornado.web from myhas ...

  9. Spring Session解决分布式Session问题的实现原理

    使用Spring Session和Redis解决分布式Session跨域共享问题 上一篇介绍了如何使用spring Session和Redis解决分布式Session跨域共享问题,介绍了一个简单的案例 ...

  10. spring boot:使用redis cluster集群作为分布式session(redis 6.0.5/spring boot 2.3.1)

    一,为什么要使用分布式session? HpptSession默认使用内存来管理Session,如果将应用横向扩展将会出现Session共享问题, 所以我们在创建web集群时,把session保存到r ...

随机推荐

  1. Blackmail

    Blackmail Arthur Hailey The chief house officer, Ogilvie, who had declared he would appear at the Cr ...

  2. Cython加密python代码防止反编译

    本方法适用于Linux环境下: 1.安装库Cython pip3 install Cython==3.0.0a10 2.编写待加密文件:hello.py import random def ac(): ...

  3. 使用 QuickTime Player 将手机投屏到旧版 Macbook pro

    由于旧版的 MacBook Pro 不支持 AirPlay,我们可以通过Mac系统自带的应用程序[QuickTime Player]来进行投屏操作. 以下是具体的步骤: 首先,使用USB数据线将你的 ...

  4. 18.2 使用NPCAP库抓取数据包

    NPCAP 库是一种用于在Windows平台上进行网络数据包捕获和分析的库.它是WinPcap库的一个分支,由Nmap开发团队开发,并在Nmap软件中使用.与WinPcap一样,NPCAP库提供了一些 ...

  5. k8s集群证书过期,重新生成证书

    Kubernetes集群证书过期后,使用kubeadm重新颁发证书 默认情况下部署kubernetes集群的证书一年内便过期,如果不及时升级证书导致证书过期,Kubernetes控制节点便会不可用,所 ...

  6. 逻辑漏洞挖掘之CSRF漏洞原理分析及实战演练

    一.前言 2月份的1.2亿条用户地址信息泄露再次给各大公司敲响了警钟,数据安全的重要性愈加凸显,这也更加坚定了我们推行安全测试常态化的决心.随着测试组安全测试常态化的推进,有更多的同事对逻辑漏洞产生了 ...

  7. Java模块化应用实践之精简JRE(内含开源)

    导语 Java9及以后的版本引入了模块化特性,但是直到今天JDK21都发布了,依然没有被大量使用起来,那么这个特性就真的没啥意义了吗? 别忘了,Java本身可是把模块化做到了极致的,所以可以利用这个特 ...

  8. video.js 视频

    http://www.cnblogs.com/lechenging/p/3858181.html

  9. HarmonyOS UI 开发

    引言 HarmonyOS 提供了强大的 UI 开发工具和组件,使开发者能够创建吸引人的用户界面.本章将详细介绍在 HarmonyOS 中应用 JS.CSS.HTML,HarmonyOS 的 UI 组件 ...

  10. .NET的各种对象在内存中如何布局[博文汇总]

    在过去一段时间里,我陆陆续续写一些关于.NET对象类型布局的文章,其中包括值类型和引用类型的内存布局.字符串对象和数组的内存布局等,这里作一个简单的汇总. [1] 如何计算一个实例占用多少内存? 我们 ...