You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 18:25:56