Spring Boot应用出现401 Unauthorized错误,求解决方案
你遇到的401未授权错误,通常是Spring Security的权限配置、JWT验证逻辑或者资源放行规则出了问题。结合你提供的代码,我帮你梳理几个关键排查点和解决方案:
一、核心问题排查方向
1. JWT过滤器是否正确集成?
你在WebSecurityConfig中使用了JwtConfigurer但未提供实现代码——如果这个配置类没有把JWT验证过滤器加入Spring Security的过滤器链,所有非放行请求都会被直接拦截返回401。
2. 静态资源放行规则是否覆盖全面?
你的Maven插件把前端dist目录复制到了target/classes/static/,意味着前端静态文件是根路径访问(比如/css/main.css),但你配置的ACCEPTED_FILES只放行根目录下的单个文件,前端子目录资源会被拦截。
3. 角色权限匹配是否错位?
hasRole("ADMIN")会自动给角色名添加ROLE_前缀,实际匹配的是ROLE_ADMIN权限。如果你的appUser中存储的权限是ADMIN(不带前缀),这个配置就会失效,导致管理员请求被拦截。
4. 前端请求是否正确携带Token?
登录接口/auth/sign-in放行后,前端需要将返回的token放在请求头(通常是Authorization: Bearer <token>)中,否则后续请求会被判定为未授权。
二、具体解决方案
1. 补全JWT配置类
先实现JwtConfigurer,将JWT过滤器注入Spring Security链:
package app.auth; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { private JwtTokenProvider jwtTokenProvider; public JwtConfigurer(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override public void configure(HttpSecurity http) throws Exception { JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); } }
再实现JwtTokenFilter处理Token验证:
package app.auth; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class JwtTokenFilter extends OncePerRequestFilter { private JwtTokenProvider jwtTokenProvider; public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = jwtTokenProvider.resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { Authentication auth = jwtTokenProvider.getAuthentication(token); if (auth != null) { SecurityContextHolder.getContext().setAuthentication(auth); } } filterChain.doFilter(request, response); } }
确保JwtTokenProvider能正确提取请求头中的Token:
public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; }
2. 调整静态资源放行规则
修改WebSecurityConfig,确保所有前端静态资源都被放行,同时优化权限配置的可读性:
@Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers("/assets/**", "/home/**", "/css/**", "/js/**", "/images/**") .antMatchers(ACCEPTED_FILES); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/auth/sign-in").permitAll() .antMatchers(ACCEPTED_FILES).permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 如果数据库中角色是不带ROLE_前缀的ADMIN,改用hasAuthority .antMatchers("/auth/admin/**", "/object/admin/**").hasAuthority("ADMIN") .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint()) .and() .apply(new JwtConfigurer(jwtTokenProvider)) .and() .httpBasic().disable(); }
3. 验证UserDetailsService的权限返回
确保你的UserDetailsService实现中,返回的用户权限集合与配置匹配:
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { appUser user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } Collection<GrantedAuthority> authorities = new ArrayList<>(); if (user.hasRule(ParamsEnum.ROLE_ADMIN.getValue())) { // 对应hasAuthority("ADMIN") authorities.add(new SimpleGrantedAuthority("ADMIN")); // 如果用hasRole("ADMIN"),需要改成new SimpleGrantedAuthority("ROLE_ADMIN") } else { authorities.add(new SimpleGrantedAuthority("USER")); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), authorities ); }
4. 前端请求携带Token
确保前端登录成功后,将Token放入请求头:
// 登录成功后保存Token localStorage.setItem('token', response.data.token); // 全局请求拦截器携带Token axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; });
三、额外检查点
- 确认
JwtTokenProvider的createToken方法正确将用户权限写入Token,getAuthentication方法能正确解析出权限。 - 检查数据库中用户的权限字段值,确保和
hasAuthority/hasRole的配置对应。
内容的提问来源于stack exchange,提问作者ali

