Spring Boot 3 + Spring Security 6 最新版本修改 Json 登录后 RememberMe 功能问题失效的解决方案
当 Spring Boot 版本更新到 3 之后,最低要求的 JDK 版本变为 17,相应的 最新版本的 Spring Security 的配置也发生了变化,一下主要讲解一些新的 Spring Security 的配置方法
1. 配置由继承WebSeucrityConfigurerAdapter变成只需添加一个SecurityFilterChain的bean即可。
- Remember Me Token Repository
@Bean
public PersistentTokenRepository tokenRepositoryByMemory() {return new InMemoryTokenRepositoryImpl();
}
- SecurityHandler 工具
public class SecurityHandler {public void onLoginSuccess(final HttpServletRequest request,final HttpServletResponse response,final Authentication authentication) {sendUtf8MessageToResponse(response, RespMessage.success("User " + authentication.getName() + " login success."));}public void onLoginFailure(final HttpServletRequest request,final HttpServletResponse response,final AuthenticationException exception) {log.error("Login failure, {}.", exception.getMessage());sendUtf8MessageToResponse(response, RespMessage.failure("Login failure: " + exception.getMessage()));}public void onAuthenticationFailure(final HttpServletRequest request,final HttpServletResponse response,final AuthenticationException exception) {log.error("Auth failure, {}.", exception.getMessage());sendUtf8MessageToResponse(response, RespMessage.failure(HttpStatus.UNAUTHORIZED, "Auth failure: " + exception.getMessage()));}public void onAccessDenied(final HttpServletRequest request,final HttpServletResponse response,final AccessDeniedException exception) {log.error("Access denied, {}.", exception.getMessage());sendUtf8MessageToResponse(response, RespMessage.failure(HttpStatus.FORBIDDEN, "Access denied: " + exception.getMessage()));}public void onLogoutSuccess(final HttpServletRequest request,final HttpServletResponse response,final Authentication authentication) {RespMessage<String> resp;if (Objects.nonNull(authentication)) {resp = RespMessage.success("Logout success: " + authentication.getName() + ".");} else {log.error("Logout failure: Unauthorized logout request.");resp = RespMessage.failure(HttpStatus.UNAUTHORIZED, "Unauthorized logout request.");}sendUtf8MessageToResponse(response, resp);}public CorsConfigurationSource configurationSource() {final CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOriginPattern("*");corsConfiguration.setAllowCredentials(true);corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.addExposedHeader("*");final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfiguration);return source;}private void sendUtf8MessageToResponse(final HttpServletResponse response,final RespMessage<?> respMessage) {response.setContentType(APPLICATION_JSON_VALUE);try {JSONUtil.toJsonStr(respMessage, response.getWriter());} catch (final IOException e) {log.error("Write To Response Failure: {}.", e.getMessage());}}
}
- UserDetailService
@Bean
public UserDetailsService userDetailsService() {final PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();final UserBuilder users = User.builder().passwordEncoder(encoder::encode);final InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();manager.createUser(users.username("user").password("password").roles("USER").build());manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());return manager;
}
- Spring Srcurity 配置类
@Bean
public SecurityFilterChain filterChain(final HttpSecurity httpSecurity) throws Exception {// 解决 Json 数据返回中文乱码问题final CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();encodingFilter.setEncoding(StandardCharsets.UTF_8.name());encodingFilter.setForceEncoding(true);return httpSecurity.addFilterBefore(encodingFilter, CsrfFilter.class).authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).formLogin(formLogin -> formLogin.loginProcessingUrl("/auth/login").successHandler(securityHandler::onLoginSuccess).failureHandler(securityHandler::onLoginFailure).permitAll()).logout(logout -> logout.logoutUrl("/auth/logout").logoutSuccessHandler(securityHandler::onLogoutSuccess)).exceptionHandling(exception -> {exception.authenticationEntryPoint(securityHandler::onAuthenticationFailure);exception.accessDeniedHandler(securityHandler::onAccessDenied);}).cors(corsConfig -> corsConfig.configurationSource(securityHandler.configurationSource())).rememberMe(rememberMeConfig -> {rememberMeConfig.rememberMeParameter("remember");rememberMeConfig.userDetailsService(userDetailsService);rememberMeConfig.tokenRepository(tokenRepository);// 设置短一点的时间以测试 remember-me 的功能rememberMeConfig.tokenValiditySeconds(30);}).csrf(AbstractHttpConfigurer::disable).sessionManagement(AbstractHttpConfigurer::disable).build();
}
- TestController ebdpoint
@GetMapping("system-resource")
public RespMessage<String> getSystemResource() {log.info("Invoke system-resource api, get resource success.");return RespMessage.success("Congratulation get the system resource.");
}
2. login 请求测试,可以看到 remember-me cookie 成功返回,并且在下一次求中会携带该 token 去服务器端,在 cookie 的有效期内直接自动登录


3. 将 login 请求更改成以 Json 字符串的格式提交的形式
- RequestUtil 工具类
public final class RequestUtil {private RequestUtil() {}public static LoginRequest getLoginRequest(final HttpServletRequest request) {final ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.readValue(request.getInputStream(), LoginRequest.class);} catch (final Exception e) {log.error("Read LoginRequest Value Error: {}.", e.getMessage());throw new AuthenticationServiceException(e.getMessage());}}
}
- 自定义 UsernamePasswordAuthenticationFilter
public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(final HttpServletRequest request,final HttpServletResponse response) throws AuthenticationException {if (!StrUtil.equalsIgnoreCase(HttpMethod.POST.name(), request.getMethod())|| !StrUtil.equalsIgnoreCase(APPLICATION_JSON_VALUE, request.getContentType())) {throw new AuthenticationServiceException("Authentication method or content type not supported: " + request.getMethod()+ ", " + request.getContentType());}final LoginRequest loginRequest = RequestUtil.getLoginRequest(request);final UsernamePasswordAuthenticationToken authRequest =new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());setDetails(request, authRequest);return getAuthenticationManager().authenticate(authRequest);}
}
- 添加自定义 UsernamePasswordAuthenticationFilter 到 IOC 容器中
private final SecurityHandler securityHandler;@Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() {final JsonUsernamePasswordAuthenticationFilter filter =new JsonUsernamePasswordAuthenticationFilter();filter.setFilterProcessesUrl("/auth/login");filter.setAuthenticationSuccessHandler(securityHandler::onLoginSuccess);filter.setAuthenticationFailureHandler(securityHandler::onLoginFailure);filter.setAuthenticationManager(authenticationManager());filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());return filter;
}
- 更改 Spring Security 配置类
// 将以下部分
.formLogin(formLogin -> formLogin.loginProcessingUrl("/auth/login").successHandler(securityHandler::onLoginSuccess).failureHandler(securityHandler::onLoginFailure).permitAll()
)
// 替换成以下部分即可
.formLogin(AbstractHttpConfigurer::disable)
.addFilterBefore(usernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
4. 发现问题:我们配置了开启了 SpringSecurity 的 remember 功能,为啥不生效了呢?
-
我们只是将 formLogin 给关闭了<
.formLogin(AbstractHttpConfigurer::disable)>,使用了自定义的 Json 格式来获取用户名和密码等信息,并且携带了 remember 的信息过来。 -
如果我们保持这个自定义登录的配置不变,仅仅只加上
.formLogin(form -> {})这一句话,其实 remember-me 功能已经解决了,虽然这可以解决问题,但这不是我们想要的,我们不就是需要自定义登录,把 formLogin 给关了吗? -
关闭了 formLogin 到底做了什么呢?就导致了 remember-me 不生效了呢,参数获取方式需要变了是一个原因,但还有其他的。
-
在源码 UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法断点查看变量发现如下:
-
使用 formLogin 配置的时候,rememberMeServices 是 PersistentTokenBaseRememberMeServices 实现的

-
使用 Json 格式自定义登录的时候,rememberMeServices 是 NullRememberMeServices 实现的

-
所以问题就出在这,当我们验证用户名密码之前,我们关闭了 formLogin 的话就没有正确配置好 rememberMeServices 的值
-
自定义 RememberMeServices
@Component public class CustomJsonRememberMeService extends PersistentTokenBasedRememberMeServices {private static final String REMEMBER_ME_ATTR_NAME = "remember";private static final Integer REMEMBER_ME_TOKEN_VALIDITY = 3600;public CustomJsonRememberMeService(final UserDetailsService userDetailsService,final PersistentTokenRepository tokenRepository) {super(UUID.randomUUID().toString(), userDetailsService, tokenRepository);setParameter(REMEMBER_ME_ATTR_NAME);setTokenValiditySeconds(REMEMBER_ME_TOKEN_VALIDITY);}@Overrideprotected boolean rememberMeRequested(final HttpServletRequest request, final String parameter) {final Object remember = request.getAttribute(REMEMBER_ME_ATTR_NAME);return Objects.nonNull(remember) && Boolean.parseBoolean(remember.toString());} }- 修改 Security remember me 部分配置
// 只需要将以下部分 .rememberMe(rememberMeConfig -> {rememberMeConfig.rememberMeParameter("remember");rememberMeConfig.userDetailsService(userDetailsService);rememberMeConfig.tokenRepository(tokenRepository);rememberMeConfig.tokenValiditySeconds(30); })// 更改成以下部分即可,这里将 userDetailsService 和 tokenRepository 都移除了是因为 customJsonRememberMeService 已经定义好了这两个的实现了 private final RememberMeServices rememberMeServices; .rememberMe(rememberMeConfig -> rememberMeConfig.rememberMeServices(rememberMeServices))- 修改 JsonUsernamePasswordAuthenticationFilter 部分
// 在获取 LoginRequest 对象之后,再从 LoginRequest 中获取 remember 的值并且存进 request 中以便 CustomJsonRememberMeService 中获取 final LoginRequest loginRequest = RequestUtil.getLoginRequest(request);// 以下是添加的代码 if (loginRequest.getRemember()) {request.setAttribute("remember", true); } -
-
再使用 PostMan 调用接口已经生效了
相关文章:
Spring Boot 3 + Spring Security 6 最新版本修改 Json 登录后 RememberMe 功能问题失效的解决方案
当 Spring Boot 版本更新到 3 之后,最低要求的 JDK 版本变为 17,相应的 最新版本的 Spring Security 的配置也发生了变化,一下主要讲解一些新的 Spring Security 的配置方法 1. 配置由继承WebSeucrityConfigurerAdapter变成只需添加一个Secur…...
Java核心知识点整理大全21-笔记
目录 18.1.5.1. upstream_module 和健康检测 18.1.5.1. proxy_pass 请求转发 18.1.6. HAProxy 19. 数据库 19.1.1. 存储引擎 19.1.1.1. 概念 19.1.1.2. InnoDB(B树) 适用场景: 19.1.1.3. TokuDB(Fractal Tree-节点带数据&…...
Redis深入理解-主从架构下内核数据结构、主从同步以及主节点选举
Redis 主从挂载后的内核数据结构分析 主节点中,会通过 clusteNode 中的 slaves 来记录该主节点包含了哪些从节点,这个 slaves 是一个指向 *clusterNode[] 数组的数据结构从节点中,会通过 clusterNode 中的 slaveof 来记录该从节点属于哪个主…...
java中BigDecimal的介绍及使用(二)
系列文章目录 java中BigDecimal的介绍及使用,BigDecimal格式化,BigDecimal常见问题java中BigDecimal的介绍及使用(二) 文章目录 系列文章目录一、前言二、BigDecimal提供的方法2.1、stripTrailingZeros() 去除小数尾部所有的02.2、int signum()2.3、int…...
NX二次开发UF_MTX3_identity 函数介绍
文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan UF_MTX3_identity Defined in: uf_mtx.h void UF_MTX3_identity(double identity_mtx [ 9 ] ) overview 概述 Returns a 3 x 3 identity matrix. 返回一个3 x 3的单位矩阵。 UFUN…...
解决Hadoop DataNode ‘Incompatible clusterIDs‘报错
问题 启动hadoop时报错Failed to add storage directory 2023-11-26 12:02:06,840 WARN common.Storage: Failed to add storage directory [DISK]file:xxx java.io.IOException: Incompatible clusterIDs in xxx/dfs/data: namenode clusterID CID-xxxxxx; datanode cluste…...
计算机毕业设计|基于SpringBoot+MyBatis框架的电脑商城的设计与实现(系统概述与环境搭建)
计算机毕业设计|基于SpringBootMyBatis框架的电脑商城的设计与实现(系统概述与环境搭建) 该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先,通过深入分析项目所需数据,包括用户、商品、商品类别、收藏、订单…...
神器!使用 patchworklib 库进行多图排版真棒啊
如果想把多个图合并放在一个图里,如图,该如何实现 好在R语言 和 Python 都有对应的解决方案, 分别是patchwork包和patchworklib库。 推介1 我们打造了《100个超强算法模型》,特点:从0到1轻松学习,原理、…...
MySQL -DDL 及表类型
DDL 创建数据库 CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification:[DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name 1.CHARACTER SET:…...
主从同步机制
RocketMQ的Broker分为Master和Slave两个角色,为了保证高可用性,Master角色的机器接收到消息后,要把内容同步到Slave机器上,这样一旦Master宕机,Slave机器依然可以提供服务。下面分析Master和Slave角色机器间同步功能实…...
Leetcode算法系列| 3. 无重复字符的最长子串
目录 1.题目2.题解C# 解法一:滑动窗口算法C# 解法二:索引寻找Java 解法一:滑动窗口算法Java 解法二:遍历字符串 1.题目 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例1: 输入: s "ab…...
Spring Cache(缓存框架)
学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…...
android开发:安卓13Wifi和热点查看与设置功能
近日对安卓热点功能做了一些技术验证,目的是想利用手机开热点给设备做初始化,用的是安卓13,简言之: 热点设置功能不可用,不可设置SSID和密码,不可程序控制开启关闭,网上的代码统统都过时了Loca…...
Java中的mysql——面试题+答案——第24期
当涉及MySQL时,面试题可以涵盖更多高级主题、安全性和实践经验。 MySQL中的存储引擎InnoDB和MyISAM的区别是什么? 答案: InnoDB支持事务,而MyISAM不支持。InnoDB使用行级锁,而MyISAM使用表级锁。InnoDB支持外键&#x…...
王者小游戏
游戏里的经验动物 Bear package beast; import sxt.GameFrame; public class Bear extends Beast {public Bear(int x, int y, GameFrame gameFrame) {super(x, y, gameFrame);setImg("C:\\Users\\辛欣\\OneDrive\\桌面\\王者荣耀图片(1)\\王者荣耀图片\\beast\\bear.jp…...
using meta-SQL 使用元SQL
%DatePart Syntax %DatePart(DTTM_Column) Description The %DatePart meta-SQL variable returns the date portion of the specified DateTime column. DatePart meta-SQL变量返回指定的DateTime列的日期部分。 Note: This meta-SQL variable is not implemented for COBOL. …...
函数式接口
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 咱们今天讨论下函数式接…...
使用shell快速查看电脑曾经连接过的WiFi密码
此方法只能查看以前连接过的wifi名称和对应的密码 查看连接过的WiFi名称netsh wlan show profiles查看具体的WiFi名称netsh wlan show profile name"你的wifi名称" keyclear...
通过亚马逊云科技云存储服务探索云原生应用的威力
文章作者:Libai 欢迎来到我们关于“使用亚马逊云科技云存储服务构建云原生应用”的文章的第一部分。在本文中,我们将深入探讨云原生应用的世界,并探索亚马逊云科技云存储服务在构建和扩展这些应用中的关键作用。 亚马逊云科技开发者社区为开发…...
Boot工程快速启动【Linux】
Boot工程快速启动【Linux】 在idea中打包cd usr/在local文件夹下mkdir app进入app文件夹把打包好的文件(只上传其中的jar)上传到app文件下检查linux中的Java版本,保证和项目的Java 版本保持一致运行 java -jar sp补全***.jar想看效果得查询当…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
