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。例如,您可以在查询中访问 authenticationprincipal。但是,如果您的角色是多值的并且您的 authentication.authorities 也是多值的(例如 M:N),我不确定如何构建查询。如果您只想提取一个角色/权限并对其进行查询,您可能会使用像 select r from Report r where ?# @reportExpressions.extractRole(authentication) member of r.roles and ... 这样的 JPA 查询。

以上是关于Spring Boot/JPA - 数据行级授权的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot JPA 连接数据库

spring boot JPA 和 数据库连接

如何禁用嵌入式数据库 Spring-boot spring-data-jpa

Spring Boot JPA 使用教程

Spring Boot / JPA / Hibernate,如何根据 Spring 配置文件切换数据库供应商?

Spring Boot JPA 找不到数据源