如何仅获取 GraphRepository 中经过身份验证的用户拥有的数据
How to only fetch data owned by an authenticated user in GraphRepository
我正在尝试使用 Spring 引导(版本 1.4.0.M2)和 Spring 数据创建 REST API Neo4j,Spring 数据休息和 Spring 安全。域由三种类型的实体组成:
- 存储 username/password 的用户实体以及与所有用户拥有的实体的关系:
@NodeEntity
public class User extends Entity {
@Relationship(type = "OWNS")
private Set<Activity> activities;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
private String username;
}
- 用户 by/owned 创建的内容实体:
@NodeEntity
public class Activity extends Entity {
private String name;
@Relationship(type = "ON", direction = Relationship.INCOMING)
private Set<Period> periods;
@Relationship(type = "HAS", direction = Relationship.INCOMING)
private User user;
}
- 存储日期和时间信息的数据实体可供所有用户访问
@NodeEntity
public class Period extends Entity {
@Relationship(type = "HAS")
private Set<Activity> activities;
@Relationship(type = "HAS_PERIOD", direction = Relationship.INCOMING)
private Day day;
private PeriodNames name;
}
我现在正在努力让一切尽可能简单,所以我只使用扩展 GraphRepository
的存储库和 Spring 使用基本身份验证和 User
Object/Repository 在 UserDetailsService
中。这按预期工作,我能够创建用户、创建一些对象等等。
问题是,我希望用户只能访问他们自己的实体。目前,每个人都可以访问所有内容。正如我从研究中了解到的那样,我有三种方法可以实现这一目标:
- 在存储库方法上使用 Spring 安全注释,如下所示:
@PostAuthorize("returnObject.user.username == principal.username")
@Override
Activity findOne(Long id);
@PostFilter("filterObject.user.username == principal.username")
@Override
Iterable<Activity> findAll();
- 使用
@Query
注释方法并使用自定义查询获取数据。
- 创建执行实际数据查询的自定义控制器和服务,类似于:sdn4-university
.
现在我的问题是:
使用我可用的工具实现所需功能的最佳方法是什么?
对我来说,这似乎是使用自定义查询 #2 的首选方式。这样我只能获取我实际需要的数据。我将不得不尝试找到一种方法来创建启用 Page<Activity> findAll(Pageable pageable)
分页的查询,但我希望这是可能的。不过,我无法在自定义查询中使用 principal.username
。好像 spring-data-neo4j doesn't have support for SpEL right now。这是正确的还是有其他方法可以在查询中访问当前经过身份验证的用户?
方式 #1,使用 Spring 安全注释对我有用(见上面的代码)但我不知道如何过滤 Page<Activity> findAll(Pageable pageable)
因为它 returns 是一个 Page
对象而不是实体或集合。此外,我不确定这种方式是否有效,因为数据库始终必须查询所有实体,而不仅仅是特定用户拥有的实体。这似乎是一种资源浪费。这不正确吗?
或者我应该直接使用 #3 并实现自定义控制器和服务?还有其他我没读到的方法吗?
非常感谢您对此主题的任何意见!
谢谢,
丹尼尔
还没有人回答,我试试看...
1) 我同意您的评估,即使用 Spring @PostAuthorize
安全性不是解决问题的方法。对于过滤数据,这似乎不是这里的完美方式。正如您所提到的,它要么加载所有数据然后对其进行过滤,从而造成沉重的负载,要么可能破坏分页机制:
假设您有上百万个结果,加载所有结果会很繁重。如果您稍后过滤它们,您可能会得到比方说 1.000 个有效结果。但我强烈怀疑分页机制是否能够解决这个问题,更有可能的是,最终你看起来会有很多空页。因此,如果您加载了比方说前 20 个结果,您最终可能会得到一个空结果,因为它们都被过滤掉了。
也许对于某些东西,如果您只想获得一个结果,您可以使用 @PreAuthorize
来防止查询发生,例如 findOne
。如果不允许,这可能会导致 403,恕我直言,没问题。但是对于过滤集合,Spring 安全性似乎不是一个好主意。
3) 这总是有可能的,但如果不尝试其他选择,我不会去那里。 Spring Data Rest 旨在让我们的代码更简洁,编码更容易,所以我们不应该在 100% 确定我们无法让它完成我们需要的事情之前就丢弃它。
2) Thomas Darimont 在 this blog posting 中写道,有一种方法可以在 @Query
注释中使用主体(和其他内容)。让我总结一下,在这里找到答案...
基本思路是创建一个新的EvaluationContextExcentionSupport
:
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}
...和...
@Configuration
@EnableJpaRepositories
class SecurityConfiguration {
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
}
...现在允许这样的 @Query
...
@Query("select o from BusinessObject o where o.owner.emailAddress like "+
"?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
对我来说,这似乎是最干净的解决方案,因为您的@Query 现在使用主体,而无需您自己编写所有控制器。
好的,我想我找到了解决办法。我想它不是很漂亮,但它现在有效。我使用 @PostAuthorize("returnObject.user.username == principal.username")
或类似的存储库方法来处理单个实体,并为 Page<Activity> findAll(Pageable pageable)
创建了一个默认实现,它只是通过调用 SecurityContextHolder.getContext().getAuthentication().getName()
获取用户名并调用一个自定义查询方法来获取正确的数据:
@RestResource(exported = false)
@Query("MATCH (u:User)-[:HAS]->(a:Activity) WHERE u.username={ username } RETURN a ORDER BY CASE WHEN NOT { sortingProperty} IS NULL THEN a[{ sortingProperty }] ELSE null END SKIP { skip } LIMIT { limit }")
List<Activity> findAllForUsernamePagedAndSorted(@Param("username") String username, @Param("sortingProperty") String sortingProperty, @Param("skip") int skip, @Param("limit") int limit);
我正在尝试使用 Spring 引导(版本 1.4.0.M2)和 Spring 数据创建 REST API Neo4j,Spring 数据休息和 Spring 安全。域由三种类型的实体组成:
- 存储 username/password 的用户实体以及与所有用户拥有的实体的关系:
@NodeEntity
public class User extends Entity {
@Relationship(type = "OWNS")
private Set<Activity> activities;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
private String username;
}
- 用户 by/owned 创建的内容实体:
@NodeEntity
public class Activity extends Entity {
private String name;
@Relationship(type = "ON", direction = Relationship.INCOMING)
private Set<Period> periods;
@Relationship(type = "HAS", direction = Relationship.INCOMING)
private User user;
}
- 存储日期和时间信息的数据实体可供所有用户访问
@NodeEntity
public class Period extends Entity {
@Relationship(type = "HAS")
private Set<Activity> activities;
@Relationship(type = "HAS_PERIOD", direction = Relationship.INCOMING)
private Day day;
private PeriodNames name;
}
我现在正在努力让一切尽可能简单,所以我只使用扩展 GraphRepository
的存储库和 Spring 使用基本身份验证和 User
Object/Repository 在 UserDetailsService
中。这按预期工作,我能够创建用户、创建一些对象等等。
问题是,我希望用户只能访问他们自己的实体。目前,每个人都可以访问所有内容。正如我从研究中了解到的那样,我有三种方法可以实现这一目标:
- 在存储库方法上使用 Spring 安全注释,如下所示:
@PostAuthorize("returnObject.user.username == principal.username")
@Override
Activity findOne(Long id);
@PostFilter("filterObject.user.username == principal.username")
@Override
Iterable<Activity> findAll();
- 使用
@Query
注释方法并使用自定义查询获取数据。 - 创建执行实际数据查询的自定义控制器和服务,类似于:sdn4-university .
现在我的问题是:
使用我可用的工具实现所需功能的最佳方法是什么?
对我来说,这似乎是使用自定义查询 #2 的首选方式。这样我只能获取我实际需要的数据。我将不得不尝试找到一种方法来创建启用 Page<Activity> findAll(Pageable pageable)
分页的查询,但我希望这是可能的。不过,我无法在自定义查询中使用 principal.username
。好像 spring-data-neo4j doesn't have support for SpEL right now。这是正确的还是有其他方法可以在查询中访问当前经过身份验证的用户?
方式 #1,使用 Spring 安全注释对我有用(见上面的代码)但我不知道如何过滤 Page<Activity> findAll(Pageable pageable)
因为它 returns 是一个 Page
对象而不是实体或集合。此外,我不确定这种方式是否有效,因为数据库始终必须查询所有实体,而不仅仅是特定用户拥有的实体。这似乎是一种资源浪费。这不正确吗?
或者我应该直接使用 #3 并实现自定义控制器和服务?还有其他我没读到的方法吗?
非常感谢您对此主题的任何意见!
谢谢, 丹尼尔
还没有人回答,我试试看...
1) 我同意您的评估,即使用 Spring @PostAuthorize
安全性不是解决问题的方法。对于过滤数据,这似乎不是这里的完美方式。正如您所提到的,它要么加载所有数据然后对其进行过滤,从而造成沉重的负载,要么可能破坏分页机制:
假设您有上百万个结果,加载所有结果会很繁重。如果您稍后过滤它们,您可能会得到比方说 1.000 个有效结果。但我强烈怀疑分页机制是否能够解决这个问题,更有可能的是,最终你看起来会有很多空页。因此,如果您加载了比方说前 20 个结果,您最终可能会得到一个空结果,因为它们都被过滤掉了。
也许对于某些东西,如果您只想获得一个结果,您可以使用 @PreAuthorize
来防止查询发生,例如 findOne
。如果不允许,这可能会导致 403,恕我直言,没问题。但是对于过滤集合,Spring 安全性似乎不是一个好主意。
3) 这总是有可能的,但如果不尝试其他选择,我不会去那里。 Spring Data Rest 旨在让我们的代码更简洁,编码更容易,所以我们不应该在 100% 确定我们无法让它完成我们需要的事情之前就丢弃它。
2) Thomas Darimont 在 this blog posting 中写道,有一种方法可以在 @Query
注释中使用主体(和其他内容)。让我总结一下,在这里找到答案...
基本思路是创建一个新的EvaluationContextExcentionSupport
:
class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
}
...和...
@Configuration
@EnableJpaRepositories
class SecurityConfiguration {
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
}
...现在允许这样的 @Query
...
@Query("select o from BusinessObject o where o.owner.emailAddress like "+
"?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
对我来说,这似乎是最干净的解决方案,因为您的@Query 现在使用主体,而无需您自己编写所有控制器。
好的,我想我找到了解决办法。我想它不是很漂亮,但它现在有效。我使用 @PostAuthorize("returnObject.user.username == principal.username")
或类似的存储库方法来处理单个实体,并为 Page<Activity> findAll(Pageable pageable)
创建了一个默认实现,它只是通过调用 SecurityContextHolder.getContext().getAuthentication().getName()
获取用户名并调用一个自定义查询方法来获取正确的数据:
@RestResource(exported = false)
@Query("MATCH (u:User)-[:HAS]->(a:Activity) WHERE u.username={ username } RETURN a ORDER BY CASE WHEN NOT { sortingProperty} IS NULL THEN a[{ sortingProperty }] ELSE null END SKIP { skip } LIMIT { limit }")
List<Activity> findAllForUsernamePagedAndSorted(@Param("username") String username, @Param("sortingProperty") String sortingProperty, @Param("skip") int skip, @Param("limit") int limit);