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

Spring Security与业务逻辑:Todo应用用户数据隔离实现问询

解决方案:实现Todo数据的用户隔离访问

首先修正你的Item实体中的明显问题,再逐步实现用户数据隔离的核心逻辑:

1. 修正Item实体定义

你的Item存在几个不符合规范的问题,修正后如下:

@Entity 
@Table(name = "item") 
public class Item {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private String description;
    private String completionDate;
    private boolean isDone; // 替换String类型为boolean,符合业务语义
    
    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false) // 指定关联字段,添加非空约束
    private User user;
    
    public Item() {}
    
    public Item(Long id, String description, String completionDate, boolean isDone, User user) {
        this.id = id;
        this.description = description;
        this.completionDate = completionDate;
        this.isDone = isDone;
        this.user = user;
    }
    
    // 修正getUser方法,返回正确的User对象
    public User getUser() {
        return user;
    }
    
    public void setUser(User user) {
        this.user = user;
    }
    
    // 遵循JavaBean规范的isDone getter
    public boolean isDone() {
        return isDone;
    }
    
    public void setDone(boolean done) {
        isDone = done;
    }
    
    // 其他getter/setter保持正常
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    public String getCompletionDate() { return completionDate; }
    public void setCompletionDate(String completionDate) { this.completionDate = completionDate; }
    
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Item other = (Item) obj;
        return Objects.equals(id, other.id);
    }
}

2. 完善安全配置

确保Todo相关接口被正确保护,同时修复H2控制台访问问题:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 可选:开启方法级权限控制
public class SpringSecurityBasicAuthConfig {

private static final Logger logger = LogManager.getLogger(SpringSecurityBasicAuthConfig.class);
    
    @Autowired
    UserDetailsService user;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        String methodName = "filterChain";
        logger.info("Executing, " + methodName);
        http.authorizeHttpRequests()
                .requestMatchers("/h2/**").permitAll()
                .requestMatchers("/api/v1/registration/**").permitAll()
                .requestMatchers("/users/**", "/api/v1/items/**").hasAuthority("USER") // 保护Todo接口
                .anyRequest().authenticated() // 其他接口强制认证
                .and()
                .formLogin()
                .and()
                .csrf().disable()
                .headers().frameOptions().sameOrigin(); // 允许H2控制台iframe访问
        return http.build();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    AuthenticationProvider authenticationProvider() 
    {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(user);
        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }
}

3. 核心业务逻辑实现

数据层:添加用户关联查询方法

在ItemRepository中定义根据用户查询Todo的方法:

public interface ItemRepository extends JpaRepository<Item, Long> {
    List<Item> findByUser(User user);
}

控制层:实现用户数据隔离

通过Principal获取当前登录用户,确保操作仅针对自身Todo:

@RestController
@RequestMapping("/api/v1/items")
public class ItemController {

    private final ItemRepository itemRepository;
    private final UserRepository userRepository;

    public ItemController(ItemRepository itemRepository, UserRepository userRepository) {
        this.itemRepository = itemRepository;
        this.userRepository = userRepository;
    }

    // 创建Todo时自动关联当前用户
    @PostMapping
    public ResponseEntity<Item> createItem(@RequestBody Item item, Principal principal) {
        User currentUser = userRepository.findByUsername(principal.getName())
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        item.setUser(currentUser);
        return ResponseEntity.ok(itemRepository.save(item));
    }

    // 查询当前用户的所有Todo
    @GetMapping
    public List<Item> getCurrentUserItems(Principal principal) {
        User currentUser = userRepository.findByUsername(principal.getName())
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        return itemRepository.findByUser(currentUser);
    }

    // 更新Todo前验证归属
    @PutMapping("/{id}")
    public ResponseEntity<Item> updateItem(@PathVariable Long id, @RequestBody Item updatedItem, Principal principal) {
        User currentUser = userRepository.findByUsername(principal.getName())
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        Item existingItem = itemRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Todo不存在"));
        
        if (!existingItem.getUser().getId().equals(currentUser.getId())) {
            throw new RuntimeException("无权限修改该Todo");
        }
        
        existingItem.setDescription(updatedItem.getDescription());
        existingItem.setCompletionDate(updatedItem.getCompletionDate());
        existingItem.setDone(updatedItem.isDone());
        return ResponseEntity.ok(itemRepository.save(existingItem));
    }

    // 删除Todo前验证归属
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteItem(@PathVariable Long id, Principal principal) {
        User currentUser = userRepository.findByUsername(principal.getName())
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        Item existingItem = itemRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Todo不存在"));
        
        if (!existingItem.getUser().getId().equals(currentUser.getId())) {
            throw new RuntimeException("无权限删除该Todo");
        }
        
        itemRepository.delete(existingItem);
        return ResponseEntity.noContent().build();
    }
}

4. 可选优化:方法级权限控制

如果想简化Controller的验证逻辑,可以用@PreAuthorize在Service层做权限校验:

@Service
public class ItemService {

    private final ItemRepository itemRepository;
    private final UserRepository userRepository;

    public ItemService(ItemRepository itemRepository, UserRepository userRepository) {
        this.itemRepository = itemRepository;
        this.userRepository = userRepository;
    }

    public Item createItem(Item item, String username) {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        item.setUser(user);
        return itemRepository.save(item);
    }

    public List<Item> getCurrentUserItems(String username) {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("用户不存在"));
        return itemRepository.findByUser(user);
    }

    @PreAuthorize("#item.user.username == authentication.name")
    public Item updateItem(Item item) {
        return itemRepository.save(item);
    }

    @PreAuthorize("@itemService.getItemById(#id).user.username == authentication.name")
    public void deleteItem(Long id) {
        itemRepository.deleteById(id);
    }

    public Item getItemById(Long id) {
        return itemRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("Todo不存在"));
    }
}

核心思路总结

  • 实体层建立Item与User的多对一关联,确保每条Todo归属明确
  • 业务操作时始终绑定当前登录用户,查询时仅返回当前用户的数据
  • 通过接口权限控制+数据归属验证,实现用户间的完全数据隔离

内容的提问来源于stack exchange,提问作者Andrew Johnson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 20:21:08