Spring项目JWT未授权报错及WebSecurityConfig废弃问题排查
Spring Boot集成JWT认证报错修复方案
运行报错日志:
ERROR security.jwt.AuthEntryPointJwt - Unauthorized error: Full authentication is required to access this resource
ERROR security.jwt.AuthTokenFilter - Cannot set user authentication {}
报错核心原因分三类:
- Spring Security 5.7+版本已完全废弃
WebSecurityConfigurerAdapter适配器,旧版继承该类的配置方式会导致AuthenticationManager无法正常注入、过滤器链加载失效 - 自定义JWT过滤器缺少Spring托管注解,内部依赖注入为null,执行时抛出空指针导致认证上下文无法设置
- 业务代码存在语法不闭合、角色赋值逻辑缺失问题,导致用户权限数据异常触发认证拦截
1. 重构安全配置类,移除废弃API
删除原有继承WebSecurityConfigurerAdapter的写法,改用Spring Security推荐的组件化Bean配置方式,同时替换已经废弃的@EnableGlobalMethodSecurity注解:
@Configuration @EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) public class WebSecurityConfig { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private AuthEntryPointJwt unauthorizedHandler; @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } // 通过AuthenticationConfiguration直接获取AuthenticationManager实例,无需重写废弃方法 @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 定义Security过滤器链,替换旧的configure(HttpSecurity)重写逻辑 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors(Customizer.withDefaults()) .csrf(csrf -> csrf.disable()) .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/food/**").permitAll() .requestMatchers("/api/cart/**").permitAll() .requestMatchers("/api/order/**").permitAll() .requestMatchers("/api/payment/**").permitAll() .requestMatchers("/h2-console/**").permitAll() .requestMatchers("/api/users/**").permitAll() .anyRequest().authenticated() ); // 兼容H2数据库控制台的frame嵌入配置 http.headers(headers -> headers.frameOptions(frame -> frame.disable())); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }
2. 修复JWT认证过滤器逻辑
原有过滤器存在两个致命问题:一是类上没有加@Component注解,手动new出来的实例不归Spring管理,内部@Autowired标记的JwtUtils、UserDetailsService全为null,执行时直接抛空指针;二是认证详情构造方式在高版本已废弃,容易引发上下文异常。修复后完整代码如下:
@Component // 必须添加该注解,否则内部依赖无法注入 public class AuthTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsServiceImpl userDetailsServiceImpl; private static final Logger LOGGER = LoggerFactory.getLogger(AuthTokenFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUsernameFromJwtToken(jwt); UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(username); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } catch (Exception e) { LOGGER.error("Cannot set user authentication: {}", e.getMessage(), e); } filterChain.doFilter(request, response); } private String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader("Authorization"); // 增加长度校验,避免字符串截取越界 if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ") && headerAuth.length() > 7) { return headerAuth.substring(7); } return null; } }
3. 修复注册接口的逻辑错误
原有注册接口存在代码块未闭合、角色赋值逻辑缺失、硬编码管理员权限的问题,会导致用户保存时角色集合为空,登录后无权限访问接口。修复后注册方法核心逻辑如下:
@PostMapping("/register") public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signupRequest) { if (userRepo.existsByEmail(signupRequest.getEmail())) { return ResponseEntity .badRequest() .body(new MessageResponse("Error: Email is already in use!")); } User user = new User(signupRequest.getEmail(), signupRequest.getName(), passwordEncoder.encode(signupRequest.getPassword()), signupRequest.getAddress()); Set<Role> roles = new HashSet<>(); String strRoles = signupRequest.getRole(); // 未指定角色默认分配普通用户权限,禁止默认给管理员权限 if (strRoles == null) { Role userRole = roleRepo.findByRoleName("ROLE_USER") .orElseThrow(() -> new RuntimeException("Error: role not found")); roles.add(userRole); } else { switch (strRoles) { case "admin": Role adminRole = roleRepo.findByRoleName("ROLE_ADMIN") .orElseThrow(() -> new RuntimeException("Error: role not found")); roles.add(adminRole); break; default: Role userRole = roleRepo.findByRoleName("ROLE_USER") .orElseThrow(() -> new RuntimeException("Error: role not found")); roles.add(userRole); break; } } user.setRoles(roles); userRepo.save(user); emailService.sendTextEmail(user.getEmail()); return ResponseEntity .status(201) .body(new MessageResponse("User created successfully")); }
修复验证
重启项目后按顺序验证:
- 调用注册接口创建测试用户,确认数据库中用户、角色关联数据正常写入
- 调用登录接口,确认可以正常返回JWT令牌
- 携带格式为
Authorization: Bearer 你的JWT令牌的请求头访问受保护接口,确认不再返回401未授权错误
内容的提问来源于stack exchange,提问作者Nitin Kumar
相关产品推荐
相关产品推荐

