我正在尝试使用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}}。这是按预期工作的,我能够创建用户,创建一些对象等等。
问题是,我希望用户只能访问自己的实体。目前,每个人都可以访问所有内容。正如我从研究中所理解的那样,我有三种方法可以实现这一目标:
@PostAuthorize("returnObject.user.username == principal.username")
@Override
Activity findOne(Long id);
@PostFilter("filterObject.user.username == principal.username")
@Override
Iterable<Activity> findAll();
@Query
注释方法并使用自定义查询来获取数据。现在我的问题:
使用我的可用工具实现所需功能的最佳方法是什么?
对我来说,它似乎是使用#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 并实施自定义控制器和服务?还有另一种我没读过的方式吗?
我非常感谢有关此主题的任何意见!
谢谢, 丹尼尔
答案 0 :(得分:4)
由于还没有人回答,让我试试......
1)我同意您的评估,即使用Spring @PostAuthorize
安全性不是这里的方法。对于过滤数据,似乎不是在这里完成它的完美方式。正如您所提到的,它会加载所有数据然后对其进行过滤,从而产生大量负载或可能破坏分页机制:
想象一下,你有一百万个结果,加载它们都会很重。如果您稍后过滤它们,最后可能会说1.000有效结果。但我强烈怀疑分页机制是否能够解决这个问题,最终你可能会看到很多空页面。因此,如果您加载了,请说出前20个结果,您可能会得到一个空结果,因为它们都被过滤掉了。
对于某些内容,如果您只想获得单个结果,可以使用@PreAuthorize
来阻止查询发生,例如findOne
。这可能会导致403,如果不允许的话,这将是好的。但是对于过滤集合,Spring安全性似乎并不是一个好主意。
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);