Spring Security认证问题:携带有效Bearer Token仍返回401错误求助
问题原因及解决方案
核心问题:缺少JWT认证过滤器配置
你的Spring Security配置仅启用了表单登录(formLogin()),但未配置JWT Token解析过滤器——Spring Security无法识别请求头中的Bearer Token,因此判定请求未授权,返回401。
具体修复步骤
1. 添加JWT认证过滤器
自定义过滤器用于提取请求头中的Token、验证有效性,并将认证信息存入SecurityContext:
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getTokenFromRequest(request); if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) { Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
配套JWT工具类负责Token生成、验证及身份信息提取:
@Component public class JwtTokenProvider { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expiration}") private long jwtExpirationMs; // 生成Token public String generateToken(Authentication authentication) { UserDetails userDetails = (UserDetails) authentication.getPrincipal(); return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } // 验证Token有效性 public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { throw new InvalidJwtAuthenticationException("无效或过期的JWT Token"); } } // 从Token获取Authentication对象 public Authentication getAuthentication(String token) { Claims claims = Jwts.parser() .setSigningKey(jwtSecret) .parseClaimsJws(token) .getBody(); String username = claims.getSubject(); UserDetails userDetails = User.withUsername(username) .password("") .authorities(getAuthoritiesFromClaims(claims)) .build(); return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); } // 从Token中提取权限列表 private Collection<? extends GrantedAuthority> getAuthoritiesFromClaims(Claims claims) { List<String> roles = claims.get("roles", List.class); return roles.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } }
2. 修改SecurityFilterChain配置
添加JWT过滤器,精简冗余的权限配置:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { return http .cors() .and().csrf().disable() // 将JWT过滤器添加到表单登录过滤器之前 .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .authorizeHttpRequests(auth -> auth .requestMatchers("/register/", "/api/test/all", "/api/test/login").permitAll() .requestMatchers("/users/").hasAnyAuthority("CUSTOMER", "ADMIN", "EXPERT") .requestMatchers("/api/test/customer").hasRole("CUSTOMER") .anyRequest().authenticated() ) // 禁用表单登录(前后端分离场景下无需保留) .formLogin().disable() .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); }) .and() .build(); }
3. 修正权限配置错误
hasRole("ROLE_CUSTOMER")写法错误:Spring Security的hasRole()会自动添加ROLE_前缀,若用户权限为CUSTOMER,应写hasRole("CUSTOMER");若权限存储为ROLE_CUSTOMER,则改用hasAuthority("ROLE_CUSTOMER")。- 原配置中重复对
/api/test/customer设置权限,合并为一条即可。
4. 开启方法级安全(若使用@PreAuthorize)
在启动类或配置类上添加@EnableMethodSecurity注解,确保@PreAuthorize("hasRole('CUSTOMER')")生效:
@SpringBootApplication @EnableMethodSecurity public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
额外检查点
- 确认Token解码后包含正确的权限字段,确保权限值与配置匹配。
- 检查Postman请求头的
Authorization格式:必须为Bearer <token>,注意Bearer后有空格。
内容的提问来源于stack exchange,提问作者Quan Pham
相关产品推荐
相关产品推荐

