Spring Security中Postman Basic Auth调用Save接口遇401问题
Spring Security权限与认证问题排查修复
问题描述
在使用Spring Security时遇到以下问题:
- 浏览器登录后可正常执行Get、Delete请求,但Save接口因需要请求体无法在浏览器直接操作;
- 使用Postman配置Basic Auth后,请求仍跳转至授权页面;
- 添加
.httpBasic(Customizer.withDefaults())后,其他接口可通过Basic Auth正常调用,但Save接口返回空内容且报401错误。
相关代码
Controller代码
@RestController @RequestMapping("/product") @Slf4j public class ProductController { private final ProductRepository productRepository; @Autowired public ProductController(ProductRepository productRepository) { this.productRepository = productRepository; } @GetMapping("/products") public List<Product> getAllProducts() { log.info("Receiving all products"); return productRepository.findAll(); } @GetMapping("/getProduct/{id}") public Product getProductById(@PathVariable Long id) { log.info("Receiving product by id"); return productRepository.getReferenceById(id); } @PostMapping("/save") public Product saveProduct(@RequestBody Product product) { log.info("Save products"); return productRepository.save(product); } @DeleteMapping("/remove/{id}") public void deleteProduct(@PathVariable Long id) { Product product = productRepository.getReferenceById(id); log.info("delete product by id"); if (product != null) { productRepository.delete(product); } } }
Security配置代码
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public InMemoryUserDetailsManager userDetailsService() { UserDetails user = User.builder() .username("user") .password(passwordEncoder().encode("user1Pass")) .roles(UserRole.USER.name()) .build(); UserDetails admin = User.builder() .username("admin") .password(passwordEncoder().encode("admin1Pass")) .roles(UserRole.USER.name()) .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authz) -> authz .requestMatchers("/product/save").hasRole(UserRole.ADMIN.name()) .requestMatchers("/product/remove/**").hasRole(UserRole.ADMIN.name()) .requestMatchers("/product/products").hasAnyRole(UserRole.ADMIN.name(), UserRole.USER.name()) .requestMatchers("/product/getProduct/{id}").permitAll() .requestMatchers("/product").permitAll() .anyRequest().authenticated() ).formLogin(withDefaults()); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
问题分析
- 权限配置错误:admin用户的角色被设置为
UserRole.USER.name(),但Save接口要求ADMIN角色,导致admin用户无权限访问Save接口,触发401错误; - 认证跳转冲突:默认
formLogin启用时,认证失败会触发页面跳转,导致Postman使用Basic Auth时仍被跳转; - CSRF拦截:Rest接口的Post请求默认会被Spring Security的CSRF防护拦截,导致请求失败。
修复步骤
1. 修正用户角色配置
将admin用户的角色改为ADMIN,确保其拥有对应权限:
@Bean public InMemoryUserDetailsManager userDetailsService() { UserDetails user = User.builder() .username("user") .password(passwordEncoder().encode("user1Pass")) .roles(UserRole.USER.name()) .build(); UserDetails admin = User.builder() .username("admin") .password(passwordEncoder().encode("admin1Pass")) .roles(UserRole.ADMIN.name()) // 修正为ADMIN角色 .build(); return new InMemoryUserDetailsManager(user, admin); }
2. 调整SecurityFilterChain配置
针对Rest接口场景,关闭CSRF防护,同时修改formLogin的处理逻辑,避免认证失败时跳转页面,保留Basic Auth功能:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // 关闭CSRF防护,适配Rest接口 .authorizeHttpRequests(authz -> authz .requestMatchers("/product/save").hasRole(UserRole.ADMIN.name()) .requestMatchers("/product/remove/**").hasRole(UserRole.ADMIN.name()) .requestMatchers("/product/products").hasAnyRole(UserRole.ADMIN.name(), UserRole.USER.name()) .requestMatchers("/product/getProduct/{id}").permitAll() .requestMatchers("/product").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .failureHandler((request, response, exception) -> { // 认证失败返回JSON响应,不跳转 response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401,\"msg\":\"认证失败\"}"); }) .successHandler((request, response, authentication) -> { // 登录成功返回JSON响应 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":200,\"msg\":\"登录成功\"}"); }) ) .httpBasic(Customizer.withDefaults()); // 启用Basic Auth return http.build(); }
3. 验证修复效果
- 使用Postman调用
/product/save接口,配置admin账号的Basic Auth,可正常提交请求并返回结果; - 使用user账号调用
/product/save接口,会返回401错误,符合权限控制预期; - 浏览器登录后,可通过AJAX方式调用Save接口,认证成功/失败均返回JSON,无页面跳转。
内容的提问来源于stack exchange,提问作者ObserverUA
相关产品推荐
相关产品推荐

