SpringSecurity导致Redis压力大问题的解决方案

 更新时间:2025年06月23日 08:28:46   作者:OnlyLowG  
在我们现有的系统中,用于登录的 Redis 服务器 CPU 占用率长期处于 90% 的高位,这一状况带来了极大的风险隐患,因此,本文旨在解决这个问题,下面小编给大家详细介绍SpringSecurity导致Redis压力大问题的解决方案,需要的朋友可以参考下

分析

总体分析

经过深入分析,我发现 Redis 的 hgetall 操作极为频繁,而这些操作主要是由 Spring Security 框架执行,主要是hgetall 获取spring:session:expirations:{timestamp}中的所有会话、一是hgetall获取的数据量过大,二是执行hgetall过于频繁。

简单介绍下springsecuriy用到的rediskey

Redis Key说明
spring:session:sessions:{sessionId}Spring Session 的默认存储 Key,用于存储每个会话的信息,其中 {sessionId} 是具体的会话 ID。
spring:session:sessions:expires:{sessionId}存储会话的过期时间戳,与具体的会话 ID 相关联。
spring:session:expirations:{timestamp}存储每分钟需要过期的会话 ID 集合,{timestamp} 是每分钟的时间戳。
spring:session:index:org.springframework.session
.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:
索引 Key,用于存储用户名与会话 ID 的映射关系,方便根据用户名快速查找对应的会话 ID 集合。

抽丝剥茧,命中难点

进一步调查发现,我们的系统目前存在两套鉴权体系,一套基于 token,另一套基于 cookie。虽然 token 鉴权体系本身并未直接使用 Redis,但由于集成了 Spring Security 框架,token 类型同样会生成 cookie,并由此对 Redis 进行读写操作。

在实际使用中,采用 cookie 鉴权方式的主要是管理系统,其使用人员相对较少;而 token 鉴权方式的使用量却极为庞大,是导致 Redis CPU 占用率居高不下的关键因素。此外,我们所有应用都默认开启了 “Spring 定时清理过期会话” 的任务,这一任务也导致了 QPS(每秒查询率)过高,进一步加剧了 Redis 的负载。

springsecurity定时清理会话任务,如果不配置,默认是每分钟执行一次

方案

分析出结果后,解决方案也呼之欲出了

定时任务处理

仅在少数特定的应用中保留 session 过期处理机制。

如果是springboot2及以上,则可以通过配置关掉。而我们用的springboot1,配置无法关掉,只能另辟蹊径,把非登录相关应用的清理时间都改为一天一次或一月一次。修改方式如下

yml中配置

spring:
  session:
    cleanup:
      cron:
        # spring session 定期清理redis关闭,这个定时任务用户中心开启即可
        # expression: '-' springboot2.0才支持
        expression: '0 02 02 ? * *'

cookie过多治理

总体方案

对于采用 token 鉴权方式的系统,不再生成 cookie,避免对 Redis 进行读写操作。

因为springsecurity默认使用spring-session中的 SessionRepositoryFilter来进行session的操作,所以我们需要生成一个自定义session处理拦截器,来覆盖springsecurity自身的处理,如果header中存在token,就不再执行SessionRepositoryFilter拦截器。就不会对redis进行任何操作。

我们需要添加拦截器,需要他在SessionManagementFilter拦截器执行前执行,然后重新打sso包,让各应用更新使用(SessionRepositoryFilter 并非 Spring Security 默认过滤器链的一部分,它通常是通过 spring-session 项目引入的,以便在分布式应用中共享会话信息。在实际使用中,SessionRepositoryFilter 通常在 SessionManagementFilter 之前执行,以便能够正确地管理和存储会话信息)

这里简单介绍下springsecurity的拦截器加载顺序

序号过滤器名称说明
1SecurityContextPersistenceFilter恢复安全上下文,从会话或请求中加载安全信息,为后续过滤器的执行奠定基础。
2HeaderWriterFilter添加安全相关的 HTTP 响应头,如内容安全策略(CSP)、HTTP 严格传输安全(HSTS)等,增强应用的安全性。
3CorsFilter处理跨域资源共享(CORS)请求,验证跨域请求的合法性。
4CsrfFilter验证 CSRF 令牌,防止跨站请求伪造攻击,保护应用免受恶意请求的侵害。
5LogoutFilter处理注销请求,使用户能够安全地退出应用,清除相关的安全上下文。
6UsernamePasswordAuthenticationFilter处理基于用户名和密码的登录请求,验证用户的身份,并在验证成功后创建认证对象。
7DefaultLoginPageGeneratingFilter如果未配置自定义登录页,生成默认的登录页面,简化了登录流程的实现。
8BasicAuthenticationFilter处理 HTTP Basic 认证,验证请求中的认证信息,并在验证成功后创建认证对象。
9BearerTokenAuthenticationFilter提取 JWT 等令牌进行认证,适用于基于令牌的认证机制。
10RequestCacheAwareFilter恢复缓存的请求,以便在用户登录后能够正确地重定向到原始请求的页面。
11SecurityContextHolderFilter使安全上下文在请求中可用,为后续的安全操作提供上下文支持。
12AnonymousAuthenticationFilter如果未找到认证信息,则分配一个匿名用户认证对象,使匿名用户也能够访问应用中的某些资源。
13SessionManagementFilter处理会话固定防护和并发控制,管理会话相关的安全策略,如防止会话劫持等。
14ExceptionTranslationFilter捕获安全异常并将其转换为相应的 HTTP 响应,如未认证或未授权的错误响应,使应用能够以合适的方式处理安全问题。
15FilterSecurityInterceptor强制执行授权规则,检查用户是否有权限访问请求的资源,是 Spring Security 授权过程的核心。

以下是为实现该解决方案所编写的代码:

生成自定义session处理拦截器

创建一个MySessionRepositoryFilter继承SessionRepositoryFilter

package com.onlylowg.config.filter;

import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.session.SessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * token filter
 */
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
@Component
public class MySessionRepositoryFilter extends SessionRepositoryFilter {


    private String tokenHeader = "Authorization";

    private String tokenHead = "bearer";

    private String accessToken = "access_token";

    /**
     * Creates a new instance.
     *
     * @param sessionRepository the <code>SessionRepository</code> to use. Cannot be null.
     */
    public MySessionRepositoryFilter(SessionRepository sessionRepository) {
        super(sessionRepository);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if (!StringUtils.isNotBlank(request.getHeader(tokenHeader))) {

            super.doFilterInternal(request, response, filterChain);
            return;
        }
        filterChain.doFilter(request, response);

    }
}

配置拦截器

配置MySessionRepositoryFilter在SessionManagementFilter拦截器执行前执行

package com.onlylowg.config;


import com.onlylowg.config.filter.MySessionRepositoryFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.session.*;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

import javax.annotation.Resource;

/**
 * spring security配置类
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//    jwt:
//    header: Authorization
//    tokenHead: bearer
//    accessToken: access_token
    private String tokenHeader = "Authorization";

    private MySessionRepositoryFilter mySessionRepositoryFilter;

    private FindByIndexNameSessionRepository sessionRepository;

     * @date 2017-08-29
     * 这里这样注入,避免被扫包时加入到fitlers中,然后手动加入拦截器,设置在UsernamePasswordAuthenticationFilter之前执行
     * 注意JwtAuthenticationTokenFilter 不能加注解,加的话,jwt会工作两次
     */
    @Bean("jwtAuthenticationTokenFilter")
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() throws Exception {
        return new JwtAuthenticationTokenFilter();
    }

    /**
     * 配置资源服务器请求相关
     *
     * @param http 请求
     * @throws Exception 异常
     * @author WangHQ
     * @date 2017-07-13
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        init();

        http
                .csrf().disable()
                //.requestMatcher(new OAuthRequestedMatcher())
                .authorizeRequests()
                .antMatchers("/**").permitAll()//允许所有访问,权限控制交由filter
                .anyRequest().authenticated()
        ;
        http.addFilterBefore(mySessionRepositoryFilter, SessionManagementFilter.class);
    }



    public void init() {
    
        mySessionRepositoryFilter = new MySessionRepositoryFilter(sessionRepository);
    }
}

以上就是SpringSecurity导致Redis压力大问题的解决方案的详细内容,更多关于SpringSecurity导致Redis压力大的资料请关注脚本之家其它相关文章!

相关文章

  • Java Thread.currentThread().getName() 和 this.getName()区别详解

    Java Thread.currentThread().getName() 和 this.getName()区别详

    本文主要介绍了Thread.currentThread().getName() 和 this.getName()区别详解,TestThread testThread = new TestThread();
    2022-02-02
  • SpringBoot获取配置信息的三种方式总结

    SpringBoot获取配置信息的三种方式总结

    这篇文章给大家介绍了SpringBoot获取配置信息的三种方式,@Value属性值注入,绑定配置类和通过 environment获取这三种方式,文中通过代码示例给大家介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2024-01-01
  • Java线程池ThreadPoolExecutor的使用及其原理详细解读

    Java线程池ThreadPoolExecutor的使用及其原理详细解读

    这篇文章主要介绍了Java线程池ThreadPoolExecutor的使用及其原理详细解读,线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务,线程池线程都是后台线程,需要的朋友可以参考下
    2023-12-12
  • 分布式下实现配置管理方式

    分布式下实现配置管理方式

    分布式配置管理平台是为了解决配置管理问题而出现的,它能够实时管理配置信息,确保配置的实时性和高可用性,配置管理平台通常基于推送-订阅模式,客户端订阅配置信息,配置中心负责推送配置数据,常见的分布式配置管理平台包括Apollo、Diamond和Disconf
    2024-12-12
  • Intellij IDEA导入eclipse web项目的操作步骤详解

    Intellij IDEA导入eclipse web项目的操作步骤详解

    Eclipse当中的web项目都会有这两个文件,但是idea当中应该是没有的,所以导入会出现兼容问题,但是本篇文章会教大家如何导入,并且导入过后还能使用tomcat运行,需要的朋友可以参考下
    2023-08-08
  • Java基础之代理原理与用法详解

    Java基础之代理原理与用法详解

    这篇文章主要介绍了Java基础之代理原理与用法,结合实例形式详细分析了java代理的概念、原理、用法及相关操作注意事项,需要的朋友可以参考下
    2020-02-02
  • ArrayList源码和多线程安全问题分析

    ArrayList源码和多线程安全问题分析

    这篇文章主要介绍了ArrayList源码和多线程安全问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面小编和大家一起来学习一下吧
    2019-05-05
  • Java中的RMI使用方法详解

    Java中的RMI使用方法详解

    这篇文章主要介绍了Java中的RMI使用方法,RMI是Java提供的一个完善的简单易用的远程方法调用框架,采用客户服务器通信方式,在服务器上部署了提供各种服务的远程对象,下面我们来详细讲解
    2023-10-10
  • Spring框架中TaskExecutor类型Bean冲突导致的自动注入失败问题的解决步骤

    Spring框架中TaskExecutor类型Bean冲突导致的自动注入失败问题的解决步骤

    Spring中多个TaskExecutor Bean冲突导致注入失败,本文给大家介绍的解决方案包括使用@Qualifier或@Primary明确注入目标、重命名Bean、排除自动配置,确保版本兼容以避免冲突,需要的朋友可以参考下
    2025-07-07
  • springboot多节点应用里的雪花算法唯一性详解

    springboot多节点应用里的雪花算法唯一性详解

    雪花算法在单节点下唯一,但在多副本Kubernetes环境中可能重复,通过修改Pod名称生成workId,解决了这个问题,同时避免了第三方组件和网络请求,本文给大家介绍springboot多节点应用里的雪花算法唯一性,感兴趣的朋友一起看看吧
    2024-12-12

最新评论