SpringSecurity導(dǎo)致Redis壓力大問(wèn)題的解決方案
分析
總體分析
經(jīng)過(guò)深入分析,我發(fā)現(xiàn) Redis 的 hgetall 操作極為頻繁,而這些操作主要是由 Spring Security 框架執(zhí)行,主要是hgetall 獲取spring:session:expirations:{timestamp}中的所有會(huì)話、一是hgetall獲取的數(shù)據(jù)量過(guò)大,二是執(zhí)行hgetall過(guò)于頻繁。
簡(jiǎn)單介紹下springsecuriy用到的rediskey
| Redis Key | 說(shuō)明 |
| spring:session:sessions:{sessionId} | Spring Session 的默認(rèn)存儲(chǔ) Key,用于存儲(chǔ)每個(gè)會(huì)話的信息,其中 {sessionId} 是具體的會(huì)話 ID。 |
| spring:session:sessions:expires:{sessionId} | 存儲(chǔ)會(huì)話的過(guò)期時(shí)間戳,與具體的會(huì)話 ID 相關(guān)聯(lián)。 |
| spring:session:expirations:{timestamp} | 存儲(chǔ)每分鐘需要過(guò)期的會(huì)話 ID 集合,{timestamp} 是每分鐘的時(shí)間戳。 |
| spring:session:index:org.springframework.session .FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME: | 索引 Key,用于存儲(chǔ)用戶名與會(huì)話 ID 的映射關(guān)系,方便根據(jù)用戶名快速查找對(duì)應(yīng)的會(huì)話 ID 集合。 |
抽絲剝繭,命中難點(diǎn)
進(jìn)一步調(diào)查發(fā)現(xiàn),我們的系統(tǒng)目前存在兩套鑒權(quán)體系,一套基于 token,另一套基于 cookie。雖然 token 鑒權(quán)體系本身并未直接使用 Redis,但由于集成了 Spring Security 框架,token 類型同樣會(huì)生成 cookie,并由此對(duì) Redis 進(jìn)行讀寫操作。
在實(shí)際使用中,采用 cookie 鑒權(quán)方式的主要是管理系統(tǒng),其使用人員相對(duì)較少;而 token 鑒權(quán)方式的使用量卻極為龐大,是導(dǎo)致 Redis CPU 占用率居高不下的關(guān)鍵因素。此外,我們所有應(yīng)用都默認(rèn)開啟了 “Spring 定時(shí)清理過(guò)期會(huì)話” 的任務(wù),這一任務(wù)也導(dǎo)致了 QPS(每秒查詢率)過(guò)高,進(jìn)一步加劇了 Redis 的負(fù)載。
springsecurity定時(shí)清理會(huì)話任務(wù),如果不配置,默認(rèn)是每分鐘執(zhí)行一次

方案
分析出結(jié)果后,解決方案也呼之欲出了
定時(shí)任務(wù)處理
僅在少數(shù)特定的應(yīng)用中保留 session 過(guò)期處理機(jī)制。
如果是springboot2及以上,則可以通過(guò)配置關(guān)掉。而我們用的springboot1,配置無(wú)法關(guān)掉,只能另辟蹊徑,把非登錄相關(guān)應(yīng)用的清理時(shí)間都改為一天一次或一月一次。修改方式如下
yml中配置
spring:
session:
cleanup:
cron:
# spring session 定期清理redis關(guān)閉,這個(gè)定時(shí)任務(wù)用戶中心開啟即可
# expression: '-' springboot2.0才支持
expression: '0 02 02 ? * *'
cookie過(guò)多治理
總體方案
對(duì)于采用 token 鑒權(quán)方式的系統(tǒng),不再生成 cookie,避免對(duì) Redis 進(jìn)行讀寫操作。
因?yàn)閟pringsecurity默認(rèn)使用spring-session中的 SessionRepositoryFilter來(lái)進(jìn)行session的操作,所以我們需要生成一個(gè)自定義session處理攔截器,來(lái)覆蓋springsecurity自身的處理,如果header中存在token,就不再執(zhí)行SessionRepositoryFilter攔截器。就不會(huì)對(duì)redis進(jìn)行任何操作。
我們需要添加攔截器,需要他在SessionManagementFilter攔截器執(zhí)行前執(zhí)行,然后重新打sso包,讓各應(yīng)用更新使用(SessionRepositoryFilter 并非 Spring Security 默認(rèn)過(guò)濾器鏈的一部分,它通常是通過(guò) spring-session 項(xiàng)目引入的,以便在分布式應(yīng)用中共享會(huì)話信息。在實(shí)際使用中,SessionRepositoryFilter 通常在 SessionManagementFilter 之前執(zhí)行,以便能夠正確地管理和存儲(chǔ)會(huì)話信息)
這里簡(jiǎn)單介紹下springsecurity的攔截器加載順序
| 序號(hào) | 過(guò)濾器名稱 | 說(shuō)明 |
| 1 | SecurityContextPersistenceFilter | 恢復(fù)安全上下文,從會(huì)話或請(qǐng)求中加載安全信息,為后續(xù)過(guò)濾器的執(zhí)行奠定基礎(chǔ)。 |
| 2 | HeaderWriterFilter | 添加安全相關(guān)的 HTTP 響應(yīng)頭,如內(nèi)容安全策略(CSP)、HTTP 嚴(yán)格傳輸安全(HSTS)等,增強(qiáng)應(yīng)用的安全性。 |
| 3 | CorsFilter | 處理跨域資源共享(CORS)請(qǐng)求,驗(yàn)證跨域請(qǐng)求的合法性。 |
| 4 | CsrfFilter | 驗(yàn)證 CSRF 令牌,防止跨站請(qǐng)求偽造攻擊,保護(hù)應(yīng)用免受惡意請(qǐng)求的侵害。 |
| 5 | LogoutFilter | 處理注銷請(qǐng)求,使用戶能夠安全地退出應(yīng)用,清除相關(guān)的安全上下文。 |
| 6 | UsernamePasswordAuthenticationFilter | 處理基于用戶名和密碼的登錄請(qǐng)求,驗(yàn)證用戶的身份,并在驗(yàn)證成功后創(chuàng)建認(rèn)證對(duì)象。 |
| 7 | DefaultLoginPageGeneratingFilter | 如果未配置自定義登錄頁(yè),生成默認(rèn)的登錄頁(yè)面,簡(jiǎn)化了登錄流程的實(shí)現(xiàn)。 |
| 8 | BasicAuthenticationFilter | 處理 HTTP Basic 認(rèn)證,驗(yàn)證請(qǐng)求中的認(rèn)證信息,并在驗(yàn)證成功后創(chuàng)建認(rèn)證對(duì)象。 |
| 9 | BearerTokenAuthenticationFilter | 提取 JWT 等令牌進(jìn)行認(rèn)證,適用于基于令牌的認(rèn)證機(jī)制。 |
| 10 | RequestCacheAwareFilter | 恢復(fù)緩存的請(qǐng)求,以便在用戶登錄后能夠正確地重定向到原始請(qǐng)求的頁(yè)面。 |
| 11 | SecurityContextHolderFilter | 使安全上下文在請(qǐng)求中可用,為后續(xù)的安全操作提供上下文支持。 |
| 12 | AnonymousAuthenticationFilter | 如果未找到認(rèn)證信息,則分配一個(gè)匿名用戶認(rèn)證對(duì)象,使匿名用戶也能夠訪問(wèn)應(yīng)用中的某些資源。 |
| 13 | SessionManagementFilter | 處理會(huì)話固定防護(hù)和并發(fā)控制,管理會(huì)話相關(guān)的安全策略,如防止會(huì)話劫持等。 |
| 14 | ExceptionTranslationFilter | 捕獲安全異常并將其轉(zhuǎn)換為相應(yīng)的 HTTP 響應(yīng),如未認(rèn)證或未授權(quán)的錯(cuò)誤響應(yīng),使應(yīng)用能夠以合適的方式處理安全問(wèn)題。 |
| 15 | FilterSecurityInterceptor | 強(qiáng)制執(zhí)行授權(quán)規(guī)則,檢查用戶是否有權(quán)限訪問(wèn)請(qǐng)求的資源,是 Spring Security 授權(quán)過(guò)程的核心。 |
以下是為實(shí)現(xiàn)該解決方案所編寫的代碼:
生成自定義session處理攔截器
創(chuàng)建一個(gè)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攔截器執(zhí)行前執(zhí)行
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
* 這里這樣注入,避免被掃包時(shí)加入到fitlers中,然后手動(dòng)加入攔截器,設(shè)置在UsernamePasswordAuthenticationFilter之前執(zhí)行
* 注意JwtAuthenticationTokenFilter 不能加注解,加的話,jwt會(huì)工作兩次
*/
@Bean("jwtAuthenticationTokenFilter")
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() throws Exception {
return new JwtAuthenticationTokenFilter();
}
/**
* 配置資源服務(wù)器請(qǐng)求相關(guān)
*
* @param http 請(qǐng)求
* @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()//允許所有訪問(wèn),權(quán)限控制交由filter
.anyRequest().authenticated()
;
http.addFilterBefore(mySessionRepositoryFilter, SessionManagementFilter.class);
}
public void init() {
mySessionRepositoryFilter = new MySessionRepositoryFilter(sessionRepository);
}
}
以上就是SpringSecurity導(dǎo)致Redis壓力大問(wèn)題的解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringSecurity導(dǎo)致Redis壓力大的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- springboot+springsecurity+mybatis+JWT+Redis?實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫(kù)登錄認(rèn)證的實(shí)現(xiàn)
- SpringSecurity+Redis認(rèn)證過(guò)程小結(jié)
相關(guān)文章
Java實(shí)現(xiàn)簡(jiǎn)單QQ聊天工具
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單QQ聊天工具,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
使用Spring Boot Mybatis 搞反向工程的步驟
這篇文章主要介紹了使用Spring Boot Mybatis 搞反向工程的步驟,幫助大家更好的理解和使用spring boot框架,感興趣的朋友可以了解下2021-01-01
SpringBoot開發(fā)實(shí)戰(zhàn)系列之動(dòng)態(tài)定時(shí)任務(wù)
在我們?nèi)粘5拈_發(fā)中,很多時(shí)候,定時(shí)任務(wù)都不是寫死的,而是寫到數(shù)據(jù)庫(kù)中,從而實(shí)現(xiàn)定時(shí)任務(wù)的動(dòng)態(tài)配置,下面這篇文章主要給大家介紹了關(guān)于SpringBoot開發(fā)實(shí)戰(zhàn)系列之動(dòng)態(tài)定時(shí)任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-08-08
解決druid監(jiān)控頁(yè)面SQL不顯示的問(wèn)題
這篇文章主要介紹了解決druid監(jiān)控頁(yè)面SQL不顯示的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
SpringBoot2零基礎(chǔ)到精通之配置文件與web開發(fā)
SpringBoot是一種整合Spring技術(shù)棧的方式(或者說(shuō)是框架),同時(shí)也是簡(jiǎn)化Spring的一種快速開發(fā)的腳手架,本篇讓我們一起學(xué)習(xí)配置文件以及web相關(guān)的開發(fā)2022-03-03

