Spring Data Rest中@PreAuthorize校验无法获取数据库原始Person实体
问题原因
Spring Data JPA的**持久化上下文(一级缓存)**会托管当前会话中的实体实例,当你调用getById时,它会直接返回上下文里已存在的updatedEntity实例(而非从数据库重新加载),导致无法对比客户端提交的修改与数据库原始状态。
解决方案
方法1:通过EntityManager分离实体后查询
注入EntityManager,先将传入的待更新实体从持久化上下文中分离,再查询数据库获取原始状态:
import javax.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Objects; @Component public class PersonAuthorizationChecker { private final PersonRepository personRepository; private final EntityManager entityManager; @Autowired public PersonAuthorizationChecker(PersonRepository personRepository, EntityManager entityManager) { this.personRepository = personRepository; this.entityManager = entityManager; } public boolean isUpdateAllowed(Person updatedEntity, PersonSecurityDetails principal) { // ADMIN角色直接放行所有操作 if (principal.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_" + Role.ADMIN))) { return true; } // 将待更新实体从持久化上下文分离,避免缓存干扰 entityManager.detach(updatedEntity); // 此时查询会从数据库加载原始实体数据 Person existingState = personRepository.getById(updatedEntity.getId()); // 检查是否修改了role属性(用Objects.equals对比集合内容,避免引用判断错误) if (!Objects.equals(updatedEntity.getRoles(), existingState.getRoles())) { return false; } // 普通用户仅能编辑自身实体 return principal.getId().equals(updatedEntity.getId()); } }
方法2:使用JPQL直接查询数据库
绕过仓库的缓存方法,用JPQL直接从数据库查询原始实体:
import javax.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Objects; @Component public class PersonAuthorizationChecker { private final EntityManager entityManager; @Autowired public PersonAuthorizationChecker(EntityManager entityManager) { this.entityManager = entityManager; } public boolean isUpdateAllowed(Person updatedEntity, PersonSecurityDetails principal) { if (principal.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_" + Role.ADMIN))) { return true; } // 用JPQL直接查询数据库,获取未被缓存的原始实体 Person existingState = entityManager.createQuery("SELECT p FROM Person p WHERE p.id = :id", Person.class) .setParameter("id", updatedEntity.getId()) .getSingleResult(); if (!Objects.equals(updatedEntity.getRoles(), existingState.getRoles())) { return false; } return principal.getId().equals(updatedEntity.getId()); } }
额外注意事项
- 集合对比修正:原代码用
!=判断roles是否修改是错误的,即使集合内容相同,引用不同也会返回true,必须用Objects.equals()对比集合实际内容。 - 登录用户判断优化:原代码中
loggedInPersonProvider.getLoggedInUser().getId() == principal.getId()逻辑冗余,principal本身就是当前登录用户,直接对比principal.getId()与updatedEntity.getId()即可。 - 事务一致性:确保权限检查方法在同一个事务中执行,否则
detach或查询操作可能无法正常工作。
内容的提问来源于stack exchange,提问作者Simon Lenz
相关产品推荐
相关产品推荐

