Spring Boot+Angular项目JWT认证失败求助
问题描述
开发基于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

