You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security角色权限配置后接口404及返回异常求助

Spring Security 3 权限控制与404问题解决思路

问题背景

使用Spring Security 3实现基于角色/权限的接口访问控制:

  • ROLE_ADMIN:拥有create/read/update/delete权限
  • ROLE_USER:拥有read/update权限
    用户注册功能正常,但存在两个问题:
  1. Postman通过Basic认证调用UserController接口,返回200状态码但无预期JSON输出
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:05:43