Spring Boot/JPA - 数据行级授权
Posted
技术标签:
【中文标题】Spring Boot/JPA - 数据行级授权【英文标题】:SpringBoot/JPA - data row level authorization 【发布时间】:2021-12-10 22:52:08 【问题描述】:我正在寻找一种使用 Spring Boot 和 JPA 基于 ROLES 和 DATA CONTENT(表行)授权访问的方法
用例如下:
Given a User with a Role 'ROLE_AAA'
When user fetches data from table 'REPORT'
Then only reports with specific 'REPORT_ROLE' association will be returned
ER
表:角色
ID | ROLE |
---|---|
1 | ROLE_AAA |
2 | ROLE_BBB |
表格:报告
ID | CONTENT |
---|---|
1 | ... |
2 | ... |
3 | ... |
4 | ... |
5 | ... |
表格:report_roles
REPORT_ID | ROLE_ID |
---|---|
1 | 1 |
2 | 1 |
3 | 2 |
4 | 2 |
5 | 1 |
在这种情况下,角色 ROLE_AAA 的用户将只收到 ID 为 1,2 和 5 的报告。
SELECT re.*
FROM report re
JOIN report_role rr
ON re.id = rr.report_id
JOIN role ro
ON ro.id = rr.role_id
WHERE ro.name = 'ROLE_AAA'
我可以使用关联角色表轻松编写查询 (@org.springframework.data.jpa.repository.Query) 加入我想从中获取数据的表,但我觉得我缺少 JPA 或 Spring Security开箱即用的功能。
非常感谢任何设计/实施建议。
更新
由于返回将被分页 (retuning Page<Object> from JPARepository
),我认为 PostFiltering 不是这里的选项。
【问题讨论】:
Spring Security 应该在 API 级别运行,并且可能不了解 JPA,所以这在这里可能没有帮助。 JPA 对安全性的处理不多,所以我还要说您需要滚动自己的查询。作为替代方案,您可以将查询或返回的实体相交并在那里应用过滤器,但自定义查询可能更容易理解并因此维护方法(实体可能被绕过,查询也可能,例如通过findById()
) .
我同意托马斯的观点。但是,如果您坚持使用 Spring Security 并且不介意将数据实际读入应用程序,您可以使用 @PostFilter
【参考方案1】:
这是我写的关于使用@Pre/Post annotations with a custom PermissionEvaluator
的旧文章。这似乎是您最好的选择,并允许您纯粹使用注释而不是自定义查询。当然,它仅在您的查询返回的报告不多时才有效(没有ro.name = 'ROLE_AAA'
过滤器)。
但是,在您的情况下,您并没有真正尝试使用 hasPermission(filterObject, 'xyz')
语法,因为您的用例是基于角色的,这意味着权限是 roles 并且已经在身份验证对象中(例如当局)和数据库(例如report_roles
)。通过用户角色和报告角色之间的 M:N 关系,您可以实现一个助手来为您进行检查,如下所示:
@RestController
public class ReportsController
@GetMapping("/reports")
@PostFilter("@reportExpressions.hasAnyRole(filterObject, authentication)")
public List<Report> getReports()
return new ArrayList<>(List.of(
new Report("Report #1", Set.of("ROLE_AAA", "ROLE_BBB")),
new Report("Report #2", Set.of("ROLE_AAA", "ROLE_CCC"))
));
@Component("reportExpressions")
public static class ReportExpressions
public boolean hasAnyRole(Report report, Authentication authentication)
Set<String> authorities = AuthorityUtils.authorityListToSet(
authentication.getAuthorities());
return report.getRoles().stream().anyMatch(authorities::contains);
public static class Report
private final String name;
private final Set<String> roles;
public Report(String name, Set<String> roles)
this.name = name;
this.roles = roles;
public String getName()
return name;
public Set<String> getRoles()
return roles;
如果当前用户拥有ROLE_AAA
,他们将看到两个报告等。我的示例使用控制器,但您可以将相同的注释应用到 JPA 存储库。
【讨论】:
不错的文章,但由于我正在处理分页(我只是在我的帖子中包含了这个额外的信息)我相信 PostFiltering 不是一个选项 请参阅 Spring Security 参考文档中的 integration with Spring Data。例如,您可以在查询中访问authentication
和 principal
。但是,如果您的角色是多值的并且您的 authentication.authorities
也是多值的(例如 M:N),我不确定如何构建查询。如果您只想提取一个角色/权限并对其进行查询,您可能会使用像 select r from Report r where ?# @reportExpressions.extractRole(authentication) member of r.roles and ...
这样的 JPA 查询。以上是关于Spring Boot/JPA - 数据行级授权的主要内容,如果未能解决你的问题,请参考以下文章
如何禁用嵌入式数据库 Spring-boot spring-data-jpa