Spring Security权限配置异常:User/Admin接口403无权限访问
问题描述
开发包含三种API访问权限的程序:Public、User和Admin,要求Spring Security根据接口路径和用户角色控制访问权限:
/public/**路径接口无需凭证校验- User和Admin接口需对应凭证及角色
测试时Public接口正常,但User和Admin接口在到达Controller和UserService前被拦截,返回403错误(已确认数据库中用户/管理员凭证正确)。
日志分析
日志显示请求被AnonymousAuthenticationFilter处理,SecurityContext被设置为匿名上下文,系统未读取数据库用户凭证:
2023-11-03T15:40:03.904+01:00 INFO 8812 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2023-11-03T15:40:03.904+01:00 INFO 8812 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2023-11-03T15:40:03.906+01:00 INFO 8812 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms 2023-11-03T15:40:03.978+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Securing GET /admin/users/ 2023-11-03T15:40:04.022+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext 2023-11-03T15:40:04.084+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.s.w.s.HttpSessionRequestCache : Saved request http://localhost:8080/admin/users/?continue to session 2023-11-03T15:40:04.085+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access 2023-11-03T15:40:04.094+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Securing GET /error 2023-11-03T15:40:04.100+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext 2023-11-03T15:40:04.103+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.s.w.s.HttpSessionRequestCache : Saved request http://localhost:8080/error?continue to session 2023-11-03T15:40:04.103+01:00 DEBUG 8812 --- [nio-8080-exec-2] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
相关代码检查
UserService中认证方法
@Override //for Spring Security and authentication measures using username and password credentials. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("trying to load user info using username {}", username); User user= repository.findByUserName(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } logger.info("Loaded user: {} with role: {}", user.getUsername(), user.getRole()); // Create and return a UserDetails object based on the retrieved User entity return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), AuthorityUtils.createAuthorityList("ROLE_" + user.getRole().name()) ); }
SecurityConfig配置
package com.easytravel.userManagementService.config; import com.easytravel.userManagementService.model.Role; import com.easytravel.userManagementService.userService.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig{ @Autowired @Lazy //lazy initialization to avoid circular dependency with springSecurity private UserService userService ; public static final Logger logger= LoggerFactory.getLogger(SecurityConfig.class); @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth, UserService userService) throws Exception{ //using configureGlobal which will use userService to configure authentication logger.info("configureGlobal method invoked"); auth.userDetailsService(userService); } //Configuring access control @Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { logger.info("securityFilterChain method invoked. checking for correct roles based on path."); http.csrf(x-> x.disable()); //Cross-site Request Forgery http .authorizeHttpRequests(authorize -> authorize .requestMatchers(HttpMethod.POST, "/public/**").permitAll() // .requestMatchers(HttpMethod.GET, "/public/**").permitAll() .requestMatchers("/user/users/**").hasAnyRole(Role.AUTH_USER.name(), Role.ADMIN.name()) .requestMatchers("/admin/users/**").hasRole(Role.ADMIN.name()) .anyRequest().authenticated() ); return http.build(); } @Bean //setting authentication with user's username and password that configured in the userService class. public DaoAuthenticationProvider daoAuthenticationProvider(UserService userService){ DaoAuthenticationProvider authenticationProvider= new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userService); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } }
解决方案
1. 添加认证方式配置
当前SecurityFilterChain仅配置了权限控制,但未指定认证入口,导致Spring Security无法获取用户凭证进行校验,直接进入匿名认证流程。需添加HTTP Basic认证(或表单登录等方式):
修改defaultSecurityFilterChain方法,添加httpBasic配置:
@Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { logger.info("securityFilterChain method invoked. checking for correct roles based on path."); http.csrf(x-> x.disable()) .httpBasic(withDefaults()) // 启用HTTP Basic认证 .authorizeHttpRequests(authorize -> authorize .requestMatchers(HttpMethod.POST, "/public/**").permitAll() .requestMatchers("/user/users/**").hasAnyRole(Role.AUTH_USER.name(), Role.ADMIN.name()) .requestMatchers("/admin/users/**").hasRole(Role.ADMIN.name()) .anyRequest().authenticated() ); return http.build(); }
2. 修复认证提供者关联
现有configureGlobal方法未指定PasswordEncoder,且与DaoAuthenticationProvider Bean存在配置冲突,建议删除configureGlobal方法,Spring会自动使用已定义的DaoAuthenticationProvider进行认证,确保密码加密校验正常。
3. 测试时携带认证凭证
在Postman测试User/Admin接口时,需添加Authorization头:
- 选择
Basic Auth类型 - 输入数据库中已注册的用户名和密码
4. 验证角色格式一致性
loadUserByUsername中角色前缀为ROLE_,而hasRole方法会自动添加该前缀,需确保Role枚举的name()值(如AUTH_USER、ADMIN)与代码逻辑匹配,避免角色校验不通过。
核心原因
系统未配置认证方式,导致Spring Security无法从请求中提取用户凭证,只能将请求标记为匿名,最终触发403权限拦截。添加认证方式并确保认证提供者正确配置后,系统会读取数据库用户信息完成校验。
内容的提问来源于stack exchange,提问作者Maryam Z

