基于Spring Boot与JPA实现角色控制表行级数据访问权限的方案咨询
实现方案推荐
以下方案均在SQL查询阶段拼接权限过滤条件,天然支持分页查询返回Page类型结果,无需内存过滤,完全适配你的需求:
方案1:Spring Data JPA + Security SpEL 表达式(最简便开箱即用)
Spring Data JPA 原生支持在@Query中引用Spring Security上下文的SpEL表达式,你只需要引入spring-security-data依赖即可直接使用:
- 首先引入依赖(Maven示例,Spring Boot版本会自动适配无需指定版本号):
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-data</artifactId> </dependency>
- 先在
Report实体中配置和Role实体的多对多关联,关联表指向你使用的report_roles表:
@ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "report_roles", joinColumns = @JoinColumn(name = "report_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private List<Role> roles;
- 直接在Repository的查询方法上写带权限过滤的JPQL即可,自动获取当前登录用户的角色信息:
public interface ReportRepository extends JpaRepository<Report, Long> { // 单角色场景 @Query("select r from Report r join r.roles role where role.name = ?#{authentication.name}") Page<Report> findAllUserAuthorizedReports(Pageable pageable); // 多角色场景修改为in即可 @Query("select r from Report r join r.roles role where role.name in ?#{authentication.authorities.![authority]}") Page<Report> findAllUserAuthorizedReports(Pageable pageable); }
方案2:JPA Specification 动态拼接条件
如果你需要更灵活的权限逻辑(比如后续要叠加其他业务过滤条件),可以用JPA Specification来动态拼接权限过滤语句,同样支持分页:
- 首先自定义Specification构造逻辑:
public class ReportSpecifications { public static Specification<Report> userAuthorized() { return (root, query, cb) -> { // 拿到当前用户所有角色 Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); List<String> roles = authorities.stream().map(GrantedAuthority::getAuthority).toList(); // 拼接关联查询和in条件 Join<Report, Role> roleJoin = root.join("roles"); return roleJoin.get("name").in(roles); }; } }
- Repository继承
JpaSpecificationExecutor接口:
public interface ReportRepository extends JpaRepository<Report, Long>, JpaSpecificationExecutor<Report> { }
- 调用时直接传入Specification和分页参数即可:
Page<Report> result = reportRepository.findAll(ReportSpecifications.userAuthorized(), PageRequest.of(0, 10));
方案3:Hibernate 全局过滤器(无侵入式全局生效)
如果你希望所有查询Report的地方都自动加权限过滤,不用每个方法单独写,可以用Hibernate原生的@Filter功能:
- 首先在
Report实体上定义过滤器:
@Entity @FilterDef(name = "reportRoleFilter", parameters = @ParamDef(name = "roles", type = String.class)) @Filter(name = "reportRoleFilter", condition = "id in (select report_id from report_roles rr join role r on rr.role_id = r.id where r.name in (:roles))") public class Report { // 实体原有字段保持不变 }
- 然后写AOP或者拦截器,在每次查询前开启过滤器,注入当前用户的角色:
@Autowired private EntityManager entityManager; @Before("execution(* com.xxx.repository.ReportRepository.*(..))") public void enableReportFilter() { Session session = entityManager.unwrap(Session.class); Filter filter = session.enableFilter("reportRoleFilter"); List<String> roles = SecurityContextHolder.getContext().getAuthentication() .getAuthorities().stream().map(GrantedAuthority::getAuthority).toList(); filter.setParameterList("roles", roles); }
开启后所有Report的查询都会自动拼接权限过滤条件,无需修改原有Repository方法,天然支持分页。
内容的提问来源于stack exchange,提问作者VeryNiceArgumentException
相关产品推荐
相关产品推荐

