如何仅在GraphRepository中获取经过身份验证的用户拥有的数据

时间:2016-05-05 19:44:03

标签: spring-security spring-data-neo4j spring-data-rest

我正在尝试使用Spring Data Neo4j,Spring Data Rest和Spring Security使用Spring Boot(版本 1.4.0.M2 )创建REST API。域由三种类型的实体组成:

  • 用户实体存储用户名/密码以及与所有用户拥有的实体的关系:
@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;
}
  • 由用户创建/拥有的内容实体:
@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对象/存储库UserDetailsService 1}}。这是按预期工作的,我能够创建用户,创建一些对象等等。

问题是,我希望用户只能访问自己的实体。目前,每个人都可以访问所有内容。正如我从研究中所理解的那样,我有三种方法可以实现这一目标:

  1. 对存储库方法使用Spring Security Annotations,如下所示:
  2. @PostAuthorize("returnObject.user.username == principal.username")
    @Override
    Activity findOne(Long id);
    
    @PostFilter("filterObject.user.username == principal.username")
    @Override
    Iterable<Activity> findAll();
    
    1. 使用@Query注释方法并使用自定义查询来获取数据。
    2. 创建执行实际数据查询的自定义控制器和服务,类似于:sdn4-university
    3. 现在我的问题:

      使用我的可用工具实现所需功能的最佳方法是什么?

      对我来说,它似乎是使用#2 (自定义查询)的首选方式。这样我只能获取实际需要的数据。我将不得不尝试找到一种方法来创建一个启用Page<Activity> findAll(Pageable pageable)分页的查询,但我希望这是可能的。我虽然无法在自定义查询中使用principal.username。好像是spring-data-neo4j doesn't have support for SpEL right now。这是正确的还是有另一种方法来访问查询中当前经过身份验证的用户?

      方式#1 ,使用Spring Security Annotations对我有用(请参阅上面的代码),但我无法弄清楚如何过滤Page<Activity> findAll(Pageable pageable)因为它返回Page对象而不是实体或集合。此外,我不确定这种方式是否有效,因为数据库始终必须查询所有实体,而不仅仅是特定用户拥有的实体。这似乎是浪费资源。这是不正确的吗?

      或者我应该选择#3 并实施自定义控制器和服务?还有另一种我没读过的方式吗?

      我非常感谢有关此主题的任何意见!

      谢谢, 丹尼尔

2 个答案:

答案 0 :(得分:4)

由于还没有人回答,让我试试......

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现在使用了主体而无需自己编写所有控制器。

答案 1 :(得分:1)

好的,我想我找到了解决方案。我想这不是很漂亮,但它现在有效。我使用@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);