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

Spring Boot+Angular项目JWT认证失败求助

Spring Boot + Angular JWT认证401问题排查与修复

问题描述

开发基于Spring Boot和Angular的Web应用时,用户登录后请求接口出现认证失败,错误信息如下:

Client-> Failed to load resource: the server responded with a status of 401 ()
Server -> AuthEntryPointJwt : Unauthorized error: Full authentication is required to access this resource

核心代码

AuthEntryPointJwt.java

public class AuthEntryPointJwt implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        logger.error("Unauthorized error: {}", authException.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
    }
}

AuthTokenFilter.java

public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
    public static final String TOKEN_PREFIX = "Bearer ";

    @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);

                if (!username.isEmpty()) {
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);
                } else {
                    logger.error("Username is null");
                }
            }else {
                logger.error("jwt is null");
            }
        } catch (Exception e) {
            logger.error("Cannot set user authentication: {}", e);
        }

        filterChain.doFilter(request, response);
    }

    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader(AUTHORIZATION);

        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith(TOKEN_PREFIX)) {
            return headerAuth.substring(7, headerAuth.length());
        }

        return null;
    }
}

JwtUtils.java

@Component
public class JwtUtils {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    @Value("${jwtSecret}")
    private String jwtSecret;

    @Value("${jwtExpirationMs}")
    private String jwtExpirationMs;

    public String generateJwtToken(Authentication authentication) {

        UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();

        return Jwts.builder()
                .setSubject((userPrincipal.getUsername()))
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + Integer.valueOf(jwtExpirationMs)))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }
} 

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserDetailsServiceImpl userDetailsService;
    
    public static final String[] AUTHENTICATED_URLS = { "/app/user/**", "/app/item/**"};
    public static final String SIGN_UP_URL = "/app/login" ;
    
    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;


    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Bean
    public AuthEntryPointJwt authenticationEntryPointJwt() {
        return new AuthEntryPointJwt();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors().and().csrf().disable()      .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests().antMatchers(SIGN_UP_URL).permitAll()
        .antMatchers(AUTHENTICATED_URLS ).permitAll()
        .anyRequest().authenticated();

        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);   
    }
    
}

排查与修复方案

1. 修正WebSecurityConfig的权限配置

你当前将需要认证的路径AUTHENTICATED_URLS设置为permitAll(),这意味着这些路径无需认证即可访问,与“登录后才能访问”的需求矛盾。如果这些路径需要登录验证,应修改为:

.antMatchers(AUTHENTICATED_URLS).authenticated()

如果实际请求的路径不在AUTHENTICATED_URLS数组中,会触发anyRequest().authenticated(),此时需确保请求携带有效Token,或者将目标路径加入数组并配置正确的权限规则。

2. 确认AuthTokenFilter中的请求头常量

检查parseJwt方法中的AUTHORIZATION是否为org.springframework.http.HttpHeaders.AUTHORIZATION(标准HTTP授权头常量),避免因拼写错误导致无法获取Token。若未导入该常量,需添加导入语句:

import org.springframework.http.HttpHeaders;
// 替换parseJwt中的代码
String headerAuth = request.getHeader(HttpHeaders.AUTHORIZATION);

3. 修复JwtUtils的过期时间类型问题

jwtExpirationMs定义为String类型,转换为Integer时若配置文件值包含非数字字符(如"86400000ms")会抛出异常,导致Token生成异常。建议直接改为Integer类型:

@Value("${jwtExpirationMs}")
private Integer jwtExpirationMs;

// 生成Token时无需手动转换
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))

4. 验证客户端Token携带是否正确

确保Angular请求时,Authorization头格式为Bearer <Token>,示例代码:

import { HttpHeaders } from '@angular/common/http';

const token = localStorage.getItem('token'); // 从登录返回中获取并存储的Token
const headers = new HttpHeaders({
  'Authorization': `Bearer ${token}`
});

this.http.get('/app/user/info', { headers }).subscribe(...);

检查Token是否为登录接口返回的有效值,未过期且未被篡改。

5. 利用日志定位问题

查看服务器日志:

  • 若输出jwt is null:说明请求未携带Authorization头,或格式不符合Bearer 前缀要求。
  • 若输出Cannot set user authentication: xxx:根据异常信息排查Token解析、签名验证或用户信息加载问题。

内容的提问来源于stack exchange,提问作者user1368271

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 14:18:17