当前位置: 首页 > 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想看效果得查询当…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...