Spring Security已认证用户访问无权限资源返回401而非403的修复
问题修复:Admin用户访问User端点返回401而非403的解决方法
问题描述
我定义了两个受保护端点forAdmin()和forUser(),分别供Admin和User角色访问。已认证的Admin能正常访问forAdmin(),User能正常访问forUser(),但Admin用户尝试访问forUser()时,返回的是401 Unauthorized(未认证),而非预期的403 Access Denied(权限不足)。
问题原因
核心问题在于Spring Security授权规则的配置顺序和异常处理逻辑的触发条件:
- 原配置中
authorizeHttpRequests被调用两次,第二次的anyRequest().authenticated()会干扰前序规则的执行优先级,导致角色校验异常被误判为未认证。 - 若UserDetails中角色格式不符合Spring Security的
hasRole规则(缺少ROLE_前缀),也会导致权限校验失败,进而触发错误的异常类型。
修复步骤
1. 合并授权规则配置
将分散的authorizeHttpRequests调用合并为一次,确保规则按优先级顺序执行:先放行公开接口,再匹配角色专属接口,最后兜底所有请求需认证。
修改后的WebSecurityConfiguration核心代码:
@Configuration //@EnableMethodSecurity(prePostEnabled = true) @EnableWebSecurity public class WebSecurityConfiguration { private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; private JwtRequestFilter jwtRequestFilter; private CustomUserDetailsService jwtService; private static final String[] ALLOWED_ACCESS = {"/authenticate", "/registerNewUser"}; @Autowired public WebSecurityConfiguration(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtRequestFilter jwtRequestFilter, CustomUserDetailsService jwtService) { this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint; this.jwtRequestFilter = jwtRequestFilter; this.jwtService = jwtService; } @Bean AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .cors(cors -> cors.disable()) // 根据实际需求调整CORS配置 .authorizeHttpRequests(authz -> authz .requestMatchers(ALLOWED_ACCESS).permitAll() .requestMatchers("/forAdmin").hasRole("Admin") .requestMatchers("/forUser").hasRole("User") .anyRequest().authenticated() ) .exceptionHandling(exceptionHandling -> exceptionHandling .accessDeniedHandler(new CustomAccessDeniedHandler()) .authenticationEntryPoint(jwtAuthenticationEntryPoint) ) .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(jwtService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
2. 确保角色格式符合Spring Security要求
Spring Security的hasRole方法会自动为角色添加ROLE_前缀,因此在CustomUserDetailsService中,需将数据库中的角色转换为带前缀的GrantedAuthority:
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } // 为角色添加ROLE_前缀,适配hasRole规则 List<GrantedAuthority> authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) .collect(Collectors.toList()); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), authorities ); }
修复原理
- 合并授权规则后,Admin用户访问
/forUser时会优先匹配hasRole("User")规则,触发权限不足的AccessDeniedException,进而被CustomAccessDeniedHandler捕获,返回403状态码。 - 修正角色格式后,
hasRole能正确匹配用户权限,避免因角色不匹配导致的异常误判。
内容的提问来源于stack exchange,提问作者Chinedu
相关产品推荐
相关产品推荐

