Spring Security 6.2.1会话登录403错误及UserDetails空指针问题
Spring Security 6.2.1 无表单登录问题修复方案
核心问题拆解
- 403 Forbidden:
hasAnyRole权限校验逻辑错误,Spring Security的hasAnyRole会自动给传入的角色名添加ROLE_前缀,若数据库存储的是ROLE_USER,直接写hasAnyRole("ROLE_USER")会实际校验ROLE_ROLE_USER,导致权限不匹配。 @AuthenticationPrincipal注入null:登录流程未正确将Authentication对象存入SecurityContext并绑定到会话,后续请求无法从会话中读取认证信息。
具体修复步骤
1. 修正权限校验规则
根据需求选择两种正确写法之一:
- 用
hasAnyRole:仅传入角色名(去掉前缀) - 用
hasAuthority:传入完整的权限字符串(保留前缀)
示例配置代码:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // 根据实际场景决定是否关闭CSRF .authorizeHttpRequests(auth -> auth // 写法1:hasAnyRole 自动补全ROLE_前缀 .requestMatchers("/modify/update").hasAnyRole("USER") // 写法2:hasAuthority 直接使用完整权限字符串 // .requestMatchers("/modify/update").hasAuthority("ROLE_USER") .anyRequest().authenticated() ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 会话式登录需保留会话 ); return http.build(); } }
2. 完善无表单登录的认证流程
自定义登录接口必须手动完成Authentication对象的创建与存储,确保会话绑定认证信息:
@RestController public class LoginController { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private SecurityContextRepository securityContextRepository; @PostMapping("/login") public ResponseEntity<Void> login(@RequestBody LoginRequest request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { // 1. 加载用户信息 UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername()); // 2. 校验密码 if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } // 3. 创建认证对象 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); // 4. 存入SecurityContext SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authToken); SecurityContextHolder.setContext(context); // 5. 保存到会话,确保后续请求能读取 securityContextRepository.saveContext(context, servletRequest, servletResponse); return ResponseEntity.ok().build(); } }
3. 验证会话与SecurityContext绑定
- 确认登录响应头包含
Set-Cookie: JSESSIONID=xxx; Path=/; HttpOnly - 后续请求携带该JSESSIONID Cookie
- 调试时可在控制器中打印认证信息:
@PostMapping("/modify/update") public ResponseEntity<String> update(HttpServletRequest request) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); System.out.println("当前认证用户:" + auth.getName()); // 业务逻辑 return ResponseEntity.ok("操作成功"); }
4. 正确使用@AuthenticationPrincipal
确保注入类型与UserDetails实现类匹配,若为自定义用户类可直接注入:
// 注入UserDetails @PostMapping("/modify/update") public ResponseEntity<String> update(@AuthenticationPrincipal UserDetails userDetails) { if (userDetails == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } return ResponseEntity.ok("更新成功,操作人:" + userDetails.getUsername()); } // 注入自定义User类(需实现UserDetails) @PostMapping("/modify/update") public ResponseEntity<String> update(@AuthenticationPrincipal CustomUser customUser) { return ResponseEntity.ok("更新成功,操作人:" + customUser.getNickname()); }
内容的提问来源于stack exchange,提问作者김민식
相关产品推荐
相关产品推荐

