Spring Security角色权限配置后接口404及返回异常求助
Spring Security 3 权限控制与404问题解决思路
问题背景
使用Spring Security 3实现基于角色/权限的接口访问控制:
- ROLE_ADMIN:拥有create/read/update/delete权限
- ROLE_USER:拥有read/update权限
用户注册功能正常,但存在两个问题:
- Postman通过Basic认证调用UserController接口,返回200状态码但无预期JSON输出
- Chrome浏览器访问同一接口,完成登录后显示Whitelabel 404错误页
核心问题
Chrome访问http://localhost:8081/user/findproduct?productid=1,登录认证通过后触发404错误。
相关代码片段
SecurityConfig.java
@Configuration //@Import({GsonConfig.class}) @EnableMethodSecurity @EnableWebSecurity(debug = true) public class SecurityConfig { @Autowired ProductUserRepository productuserrepo; @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); } @Bean public UserDetailsService userDetailsService() { return username -> productuserrepo.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(csrf -> csrf.disable()) .cors(cors -> cors.disable()) .authorizeHttpRequests(a -> { a.requestMatchers("/auth/**").permitAll(); a.anyRequest().authenticated(); }) .formLogin(login -> { login.loginProcessingUrl("/login") .failureForwardUrl("/loginfailure"); }) .logout(logout ->{ logout.logoutUrl("/logout") .deleteCookies("JSESSIONID") .invalidateHttpSession(false) .logoutSuccessUrl("/successlogout") .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext()); }) .authenticationManager(authManager()) .build(); } @Bean public AuthenticationManager authManager(){ DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider(); daoProvider.setUserDetailsService(userDetailsService()); daoProvider.setPasswordEncoder(encoder()); return new ProviderManager(daoProvider); } }
Role.java
@RequiredArgsConstructor public enum Role { USER(Set.of(USER_READ,USER_UPDATE)), ADMIN(Set.of(ADMIN_READ,ADMIN_UPDATE,ADMIN_DELETE,ADMIN_CREATE, USER_READ,USER_UPDATE)); @Getter private final Set<Permission> permissions; public List<SimpleGrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = getPermissions().stream() .map(permission -> new SimpleGrantedAuthority(permission.getPermission())) .collect(Collectors.toList()); authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name())); return authorities; } }
Permission.java
public enum Permission { ADMIN_READ("admin:read"), ADMIN_UPDATE("admin:update"), ADMIN_CREATE("admin:create"), ADMIN_DELETE("admin:delete"), USER_READ("user:read"), USER_UPDATE("user:update"); @Getter private final String permission; }
UserController.java
@RestController @RequestMapping("/user") @PreAuthorize("hasAnyRole('USER','ADMIN')") public class UserController { @PreAuthorize("hasAnyAuthority('admin:read','user:read')") @GetMapping(value = "/findproduct",produces = MediaType.APPLICATION_JSON_VALUE, headers = "Accept=application/json",consumes = MediaType.APPLICATION_JSON_VALUE) public Product findproductbyid(@RequestParam("productid")int productid) { System.out.println("inside findproduct method"); return prodrepo.findById(productid).get(); } }
AuthController.java
@RestController @RequestMapping("/auth") public class AuthController { @Autowired ProductUserRepository produserrepo; @Autowired PasswordEncoder pwd_encoder; @Autowired Gson gson; @PostMapping(value = "/register",produces = MediaType.APPLICATION_JSON_VALUE, headers = "Accept=application/json",consumes = MediaType.APPLICATION_JSON_VALUE) public String register(@RequestBody ProductUserModel user) { if (produserrepo.existsByUsername(user.getUsername())) { return user.getUsername() + " already taken"; }else { System.out.println(user.toString()); user.setPassword(pwd_encoder.encode(user.getPassword())); produserrepo.save(gson.fromJson(gson.toJson(user), ProductUser.class)); return user.getUsername() + " succesfully registered"; } }
解决思路
1. 修复Chrome登录后404问题
直接原因:UserController的@GetMapping添加了headers = "Accept=application/json"限制,而Chrome浏览器发起的请求Accept头为text/html,application/xhtml+xml,...,不包含application/json,导致接口无法匹配,触发404。
解决方案:
去掉headers参数限制,修改后的接口注解:
@PreAuthorize("hasAnyAuthority('admin:read','user:read')") @GetMapping(value = "/findproduct",produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public Product findproductbyid(@RequestParam("productid")int productid) { // ... 原有逻辑 }
2. 修复Postman返回200但无JSON问题
- 检查请求头:确保Postman请求添加了
Accept: application/json头,否则接口可能返回空或非JSON格式; - 处理数据不存在异常:
prodrepo.findById(productid).get()会在数据不存在时抛出NoSuchElementException,Spring默认会将异常转为500错误,但如果全局异常处理配置不当,可能返回空。改为:return prodrepo.findById(productid) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found")); - 验证序列化配置:确保
Product类有无参构造函数、所有字段有getter方法,或使用Lombok的@Data注解,保证JSON序列化正常。
3. 优化SecurityConfig配置
- 补全缺失的端点:配置的
failureForwardUrl("/loginfailure")和logoutSuccessUrl("/successlogout")没有对应控制器实现,要么添加对应端点,要么改为:.formLogin(login -> { login.loginProcessingUrl("/login") .failureUrl("/login?error"); // 使用默认错误跳转 }) .logout(logout ->{ logout.logoutUrl("/logout") .deleteCookies("JSESSIONID") .invalidateHttpSession(true) // 建议设为true,销毁会话 .logoutSuccessUrl("/"); // 跳转到存在的路径 }) - 启用Basic认证:为方便Postman测试,在SecurityFilterChain中添加HTTP Basic认证支持:
.httpBasic(Customizer.withDefaults()) - 验证UserDetails实现:确保
ProductUser类正确实现UserDetails接口,且getAuthorities()方法调用Role.getAuthorities(),保证权限信息正确注入。
4. 权限注解验证
确认@EnableMethodSecurity已启用(代码中已添加),Spring Security会自动解析@PreAuthorize注解。可通过开启@EnableWebSecurity(debug = true)查看认证日志,验证用户权限是否正确加载。
内容的提问来源于stack exchange,提问作者Sayantan Chatterjee
相关产品推荐
相关产品推荐

