当前位置: 首页 > news >正文

Spring Boot 3 + Spring Security 6 最新版本修改 Json 登录后 RememberMe 功能问题失效的解决方案

当 Spring Boot 版本更新到 3 之后,最低要求的 JDK 版本变为 17,相应的 最新版本的 Spring Security 的配置也发生了变化,一下主要讲解一些新的 Spring Security 的配置方法

1. 配置由继承WebSeucrityConfigurerAdapter变成只需添加一个SecurityFilterChainbean即可。

  1. Remember Me Token Repository
@Bean
public PersistentTokenRepository tokenRepositoryByMemory() {return new InMemoryTokenRepositoryImpl();
}
  1. 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());}}
}
  1. 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;
}
  1. 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();
}
  1. 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 字符串的格式提交的形式

  1. 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());}}
}
  1. 自定义 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);}
}
  1. 添加自定义 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;
}
  1. 更改 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 功能,为啥不生效了呢?

  1. 我们只是将 formLogin 给关闭了<.formLogin(AbstractHttpConfigurer::disable)>,使用了自定义的 Json 格式来获取用户名和密码等信息,并且携带了 remember 的信息过来。

  2. 如果我们保持这个自定义登录的配置不变,仅仅只加上.formLogin(form -> {})这一句话,其实 remember-me 功能已经解决了,虽然这可以解决问题,但这不是我们想要的,我们不就是需要自定义登录,把 formLogin 给关了吗?

  3. 关闭了 formLogin 到底做了什么呢?就导致了 remember-me 不生效了呢,参数获取方式需要变了是一个原因,但还有其他的。

  4. 在源码 UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法断点查看变量发现如下:

    1. 使用 formLogin 配置的时候,rememberMeServices 是 PersistentTokenBaseRememberMeServices 实现的
      在这里插入图片描述

    2. 使用 Json 格式自定义登录的时候,rememberMeServices 是 NullRememberMeServices 实现的
      在这里插入图片描述

    3. 所以问题就出在这,当我们验证用户名密码之前,我们关闭了 formLogin 的话就没有正确配置好 rememberMeServices 的值

    4. 自定义 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());}
    }
    
    1. 修改 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))
    
    1. 修改 JsonUsernamePasswordAuthenticationFilter 部分
    // 在获取 LoginRequest 对象之后,再从 LoginRequest 中获取 remember 的值并且存进 request 中以便 CustomJsonRememberMeService 中获取
    final LoginRequest loginRequest = RequestUtil.getLoginRequest(request);// 以下是添加的代码
    if (loginRequest.getRemember()) {request.setAttribute("remember", true);
    }
    
  5. 再使用 PostMan 调用接口已经生效了

相关文章:

Spring Boot 3 + Spring Security 6 最新版本修改 Json 登录后 RememberMe 功能问题失效的解决方案

当 Spring Boot 版本更新到 3 之后&#xff0c;最低要求的 JDK 版本变为 17&#xff0c;相应的 最新版本的 Spring Security 的配置也发生了变化&#xff0c;一下主要讲解一些新的 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&#xff08;B树&#xff09; 适用场景&#xff1a; 19.1.1.3. TokuDB&#xff08;Fractal Tree-节点带数据&…...

Redis深入理解-主从架构下内核数据结构、主从同步以及主节点选举

Redis 主从挂载后的内核数据结构分析 主节点中&#xff0c;会通过 clusteNode 中的 slaves 来记录该主节点包含了哪些从节点&#xff0c;这个 slaves 是一个指向 *clusterNode[] 数组的数据结构从节点中&#xff0c;会通过 clusterNode 中的 slaveof 来记录该从节点属于哪个主…...

java中BigDecimal的介绍及使用(二)

系列文章目录 java中BigDecimal的介绍及使用&#xff0c;BigDecimal格式化&#xff0c;BigDecimal常见问题java中BigDecimal的介绍及使用(二) 文章目录 系列文章目录一、前言二、BigDecimal提供的方法2.1、stripTrailingZeros() 去除小数尾部所有的02.2、int signum()2.3、int…...

NX二次开发UF_MTX3_identity 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;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框架的电脑商城的设计与实现&#xff08;系统概述与环境搭建&#xff09; 该项目分析着重于设计和实现基于SpringBootMyBatis框架的电脑商城。首先&#xff0c;通过深入分析项目所需数据&#xff0c;包括用户、商品、商品类别、收藏、订单…...

神器!使用 patchworklib 库进行多图排版真棒啊

如果想把多个图合并放在一个图里&#xff0c;如图&#xff0c;该如何实现 好在R语言 和 Python 都有对应的解决方案&#xff0c; 分别是patchwork包和patchworklib库。 推介1 我们打造了《100个超强算法模型》&#xff0c;特点&#xff1a;从0到1轻松学习&#xff0c;原理、…...

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&#xff1a…...

主从同步机制

RocketMQ的Broker分为Master和Slave两个角色&#xff0c;为了保证高可用性&#xff0c;Master角色的机器接收到消息后&#xff0c;要把内容同步到Slave机器上&#xff0c;这样一旦Master宕机&#xff0c;Slave机器依然可以提供服务。下面分析Master和Slave角色机器间同步功能实…...

Leetcode算法系列| 3. 无重复字符的最长子串

目录 1.题目2.题解C# 解法一&#xff1a;滑动窗口算法C# 解法二&#xff1a;索引寻找Java 解法一&#xff1a;滑动窗口算法Java 解法二&#xff1a;遍历字符串 1.题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例1: 输入: s "ab…...

Spring Cache(缓存框架)

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…...

android开发:安卓13Wifi和热点查看与设置功能

近日对安卓热点功能做了一些技术验证&#xff0c;目的是想利用手机开热点给设备做初始化&#xff0c;用的是安卓13&#xff0c;简言之&#xff1a; 热点设置功能不可用&#xff0c;不可设置SSID和密码&#xff0c;不可程序控制开启关闭&#xff0c;网上的代码统统都过时了Loca…...

Java中的mysql——面试题+答案——第24期

当涉及MySQL时&#xff0c;面试题可以涵盖更多高级主题、安全性和实践经验。 MySQL中的存储引擎InnoDB和MyISAM的区别是什么&#xff1f; 答案&#xff1a; InnoDB支持事务&#xff0c;而MyISAM不支持。InnoDB使用行级锁&#xff0c;而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. …...

函数式接口

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 咱们今天讨论下函数式接…...

使用shell快速查看电脑曾经连接过的WiFi密码

此方法只能查看以前连接过的wifi名称和对应的密码 查看连接过的WiFi名称netsh wlan show profiles查看具体的WiFi名称netsh wlan show profile name"你的wifi名称" keyclear...

通过亚马逊云科技云存储服务探索云原生应用的威力

文章作者&#xff1a;Libai 欢迎来到我们关于“使用亚马逊云科技云存储服务构建云原生应用”的文章的第一部分。在本文中&#xff0c;我们将深入探讨云原生应用的世界&#xff0c;并探索亚马逊云科技云存储服务在构建和扩展这些应用中的关键作用。 亚马逊云科技开发者社区为开发…...

Boot工程快速启动【Linux】

Boot工程快速启动【Linux】 在idea中打包cd usr/在local文件夹下mkdir app进入app文件夹把打包好的文件&#xff08;只上传其中的jar&#xff09;上传到app文件下检查linux中的Java版本&#xff0c;保证和项目的Java 版本保持一致运行 java -jar sp补全***.jar想看效果得查询当…...

浙江金华车间酷热难挡?蒸发冷省电空调能否解决降温难题?

浙江金华的夏季&#xff0c;车间内酷热难挡是许多企业面临的难题。高温不仅让员工工作体验变差&#xff0c;还可能影响生产效率。这时&#xff0c;蒸发冷省电空调成为备受关注的解决方案。蒸发冷省电空调的制冷原理有其独特之处。它需要压缩机、制冷剂进行内循环制冷。压缩机作…...

如何5分钟掌握AI化学合成规划:AiZynthFinder终极实战指南

如何5分钟掌握AI化学合成规划&#xff1a;AiZynthFinder终极实战指南 【免费下载链接】aizynthfinder A tool for retrosynthetic planning 项目地址: https://gitcode.com/gh_mirrors/ai/aizynthfinder 还在为复杂分子合成路线设计而烦恼吗&#xff1f;&#x1f914; 传…...

湍流涡旋的数值模拟方法与应用场景解析

1. 湍流涡旋的数值模拟方法解析 我第一次接触湍流数值模拟是在研究生阶段&#xff0c;当时用OpenFOAM模拟飞机翼型周围的流动&#xff0c;结果发现计算资源根本不够用——这就是典型的DNS方法带来的困扰。湍流模拟的核心挑战在于如何平衡精度与计算成本&#xff0c;目前主流方法…...

付费墙突破技术深度解析:从原理到实战的完整指南

付费墙突破技术深度解析&#xff1a;从原理到实战的完整指南 在数字化阅读成为主流的今天&#xff0c;付费墙已成为内容平台保护版权的主要手段。然而&#xff0c;技术的进步也为用户提供了相应的解决方案。本文将深入剖析付费墙突破技术的核心原理&#xff0c;并提供从基础到进…...

工业领域再发力,麒麟信安树立自主创新基础软件规模化应用又一新标杆

当前&#xff0c;随着我国工业数字化、智能化转型持续深入&#xff0c;基础软件的自主创新实践成为保障产业链安全的关键一环。麒麟信安作为基础软件代表厂商&#xff0c;正加速在工业关键场景的纵深布局&#xff0c;已与上下游厂家联合推进工业软硬件全栈自主解决方案&#xf…...

记一次系统环境变量更改后在IDEA中无法读取新值的排查过程

问题背景本人在测试Protocol buffers序列化工具时&#xff0c;将项目设置为了 JDK 22 版本&#xff0c;如图所示&#xff1a;问题现象但在执行时&#xff0c;因为涉及到需要手动执行 mvn clean compile 命令&#xff0c;但是我的本地 JDK 环境是为了兼容之前项目而设置的1.8版本…...

【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫,从单线程到高效并发的全方位升级

【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫&#xff0c;从单线程到高效并发的全方位升级 近期完成了笔趣阁小说爬虫的重构&#xff0c;从最初的Requests单线程版本&#xff0c;升级为Scrapy框架版本&#xff0c;过程中深刻体会到两者在开发效率、运行性能、代码可维护…...

从付费软件到自主开发:我用AI和FFmpeg实现了一个录屏工具亮

我为什么会发出这个疑问呢&#xff1f;是因为我研究Web开发中的一个问题时&#xff0c;HTTP请求体在 Filter&#xff08;过滤器&#xff09;处被读取了之后&#xff0c;在 Controller&#xff08;控制层&#xff09;就读不到值了&#xff0c;使用 RequestBody 的时候。 无论是…...

AI原生软件技术债爆发前夜:92%的GenAI项目在V1.5版本后陷入交付瘫痪,你中招了吗?

第一章&#xff1a;AI原生软件技术债的本质与临界征兆 2026奇点智能技术大会(https://ml-summit.org) AI原生软件的技术债并非传统工程债务的简单延伸&#xff0c;而是由模型-代码耦合失衡、数据契约隐式化、推理路径不可观测等结构性缺陷共同催生的“认知性负债”。当开发团队…...

Claude Code 常用命令大全

Claude Code 的命令体系主要分为三类&#xff1a;在终端中执行的 CLI 命令、在交互界面内使用的 斜杠命令 和用于快速操作的 键盘快捷键。⌨️ CLI 命令这类命令在启动 Claude Code 的终端中直接执行&#xff0c;用于启动、配置和管理会话。claude&#xff1a;在当前目录启动一…...