You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Spring Boot 2 WebFlux(Netty)的无后端存储会话管理咨询

实现Spring WebFlux客户端加密Cookie会话(无后端存储/粘性会话)

刚好做过类似的需求!在Spring Boot 2 + WebFlux(Netty)环境下,要实现Play框架那样的客户端会话(会话数据存在Cookie中,随请求携带),完全可以通过自定义WebSessionManager来实现,全程不需要依赖数据库/Redis,也不用开粘性会话。下面是具体的实现方案:

核心思路

把会话数据(比如用户ID、权限标识等)序列化为JSON,加密后存储在HTTP Cookie中;每个请求到达时,服务端从Cookie读取加密数据,解密、反序列化得到会话对象;响应时再把更新后的会话数据加密写回Cookie。这样所有节点都能独立处理请求,无需粘性会话,也不用后端存储。

具体实现步骤

1. 配置加密Bean(确保会话数据安全)

首先需要一个安全的加密工具,用来加密/解密会话数据。这里用Spring Security的AesBytesEncryptor(Spring Boot WebFlux项目一般已间接依赖spring-security-core):

@Configuration
public class EncryptionConfig {
    @Bean
    public BytesEncryptor cookieSessionEncryptor(@Value("${session.cookie.secret-key}") String secretKey) {
        // 生产环境:密钥必须从安全渠道获取(比如环境变量、密钥管理服务),绝对不能硬编码!
        // 密钥需要是256位(32字节),如果用字符串要确保长度足够
        return new AesBytesEncryptor(secretKey, KeyGenerators.string().generateKey());
    }
}

2. 自定义WebSession实现

实现WebSession接口,封装会话数据,同时支持序列化/反序列化:

public class CookieWebSession implements WebSession {
    private final String id;
    private final Map<String, Object> attributes = new ConcurrentHashMap<>();
    private Instant creationTime = Instant.now();
    private Instant lastAccessTime = Instant.now();
    private boolean started = true;
    private boolean expired = false;
    private Duration maxIdleTime = Duration.ofMinutes(30);

    // 构造方法:生成唯一会话ID
    public CookieWebSession() {
        this.id = UUID.randomUUID().toString();
    }

    // 实现WebSession的关键方法
    @Override
    public String getId() {
        return id;
    }

    @Override
    public <T> T getAttribute(String name) {
        return (T) attributes.get(name);
    }

    @Override
    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
    }

    @Override
    public boolean isExpired() {
        if (expired) return true;
        // 检查是否超过最大空闲时间
        return lastAccessTime.plus(maxIdleTime).isBefore(Instant.now());
    }

    @Override
    public void setLastAccessTime(Instant lastAccessTime) {
        this.lastAccessTime = lastAccessTime;
    }

    // 其他方法(比如invalidate、getMaxIdleTime等)按需实现
}

3. 自定义WebSessionManager(核心逻辑)

这个类负责从Cookie读取/写入会话数据,处理加密、解密、序列化逻辑:

@Component
public class CookieWebSessionManager implements WebSessionManager {
    private static final String SESSION_COOKIE_NAME = "WEBFLUX_CLIENT_SESSION";
    private final BytesEncryptor encryptor;
    private final ObjectMapper objectMapper;

    // 构造注入加密器和Jackson的ObjectMapper
    public CookieWebSessionManager(BytesEncryptor encryptor, ObjectMapper objectMapper) {
        this.encryptor = encryptor;
        this.objectMapper = objectMapper;
    }

    @Override
    public Mono<WebSession> getSession(ServerWebExchange exchange) {
        // 尝试从请求Cookie中获取会话数据
        return Mono.justOrEmpty(exchange.getRequest().getCookies().getFirst(SESSION_COOKIE_NAME))
                .map(Cookie::getValue)
                .flatMap(this::decryptAndDeserializeSession)
                // 如果Cookie不存在或解析失败,创建新会话
                .switchIfEmpty(Mono.defer(this::createNewSession))
                .doOnNext(session -> {
                    // 更新会话最后访问时间
                    session.setLastAccessTime(Instant.now());
                    // 将更新后的会话加密后写回响应Cookie
                    serializeAndEncryptSession(session)
                            .map(encryptedValue -> buildSessionCookie(encryptedValue, exchange))
                            .doOnNext(cookie -> exchange.getResponse().addCookie(cookie))
                            .subscribe();
                });
    }

    // 解密并反序列化会话数据
    private Mono<CookieWebSession> decryptAndDeserializeSession(String encryptedValue) {
        try {
            byte[] decryptedBytes = encryptor.decrypt(Base64.getDecoder().decode(encryptedValue));
            String sessionJson = new String(decryptedBytes, StandardCharsets.UTF_8);
            CookieWebSession session = objectMapper.readValue(sessionJson, CookieWebSession.class);
            // 如果会话已过期,创建新会话
            return session.isExpired() ? createNewSession() : Mono.just(session);
        } catch (Exception e) {
            // 解密/反序列化失败(比如数据篡改、密钥变更),直接返回新会话
            return createNewSession();
        }
    }

    // 序列化并加密会话数据
    private Mono<String> serializeAndEncryptSession(CookieWebSession session) {
        try {
            String sessionJson = objectMapper.writeValueAsString(session);
            byte[] encryptedBytes = encryptor.encrypt(sessionJson.getBytes(StandardCharsets.UTF_8));
            return Mono.just(Base64.getEncoder().encodeToString(encryptedBytes));
        } catch (JsonProcessingException e) {
            return Mono.error(e);
        }
    }

    // 创建新会话
    private Mono<CookieWebSession> createNewSession() {
        return Mono.just(new CookieWebSession());
    }

    // 构建响应Cookie(安全配置很重要!)
    private ResponseCookie buildSessionCookie(String value, ServerWebExchange exchange) {
        return ResponseCookie.from(SESSION_COOKIE_NAME, value)
                .httpOnly(true) // 禁止JS读取,防范XSS攻击
                .secure(exchange.getRequest().isSecure()) // HTTPS环境下必须开启,防止Cookie被明文传输
                .path("/") // 全局生效
                .maxAge(Duration.ofMinutes(30)) // 和会话最大空闲时间保持一致
                .sameSite(SameSite.LAX) // 防范CSRF攻击,根据业务需求选择Strict/LAX/NONE
                .build();
    }
}

4. 配置WebFlux使用自定义SessionManager

最后,让WebFlux使用我们的自定义CookieWebSessionManager:

@Configuration
public class WebFluxSessionConfig implements WebFluxConfigurer {
    private final CookieWebSessionManager cookieWebSessionManager;

    public WebFluxSessionConfig(CookieWebSessionManager cookieWebSessionManager) {
        this.cookieWebSessionManager = cookieWebSessionManager;
    }

    @Override
    public void configureWebSessionManager(WebSessionManagerConfigurer configurer) {
        configurer.sessionManager(cookieWebSessionManager);
    }
}

关键安全与性能提示

  • Cookie安全配置:必须开启httpOnly、secure(HTTPS环境)、SameSite,这是防范XSS、CSRF和明文传输的基础。
  • 密钥安全:绝对不能硬编码密钥,生产环境要通过环境变量、Spring Cloud Config或专业密钥管理服务(比如AWS KMS)获取。
  • 会话数据大小:Cookie有4KB左右的大小限制,所以只能存储必要的信息(比如用户ID、权限标识),不要存大量业务数据。
  • 加密算法选择:建议用AES-256-GCM(AesBytesEncryptor默认就是这个),兼具安全性和性能。
  • 会话过期处理:每次请求都会更新Cookie的过期时间,确保用户活跃时会话不会失效。

总结

这样实现后,你就可以在控制器里正常使用WebSession了,和普通会话用法完全一致,但会话数据是存在客户端Cookie中的,不需要后端存储,也不用配置粘性会话,完美匹配你的需求。

内容的提问来源于stack exchange,提问作者mahanhz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 03:54:30