【问题标题】:Spring data rest findBy securityCheck for nested entitySpring数据休息findBy securityCheck嵌套实体
【发布时间】:2018-05-28 11:09:26
【问题描述】:

我将 Spring-data-rest 用于我的 REST 接口,并且我已经在暴露的端点上实现了自定义安全检查。一切正常,但现在我遇到了一些不平凡的场景,我想知道 spring data rest 是否能够解决这个问题。

我有以下型号:

@Entity
public class Cycle {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "unique_cycle_id")
  private long id;

  @Column(name = "user_id")
  private long userId;

  ...

  @OneToMany(cascade = CascadeType.ALL)
  @JoinTable(name = "cycles_records", joinColumns = @JoinColumn(name = "unique_cycle_id"),
      inverseJoinColumns = @JoinColumn(name = "unique_record_id"))
  private List<Record> records;
}

@Entity
public class Record {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "unique_record_id")
  private long id;

  ...
}

在获取循环时,我会检查登录用户的 id 是否与我的 Cycle 实体中的 userId 相同。

我已经像这样实施了 cystom 安全检查:

@Slf4j
@Component
public class SecurityCheck {

  public boolean check(Record record, Authentication authentication) {
    log.debug("===Security check for record===");
    if (record == null || record.getCycle() == null) {
      return false;
    }

    return Long.toString(record.getCycle().getUserId()).equals(authentication.getName());
  }

  public boolean check(Cycle cycle, Authentication authentication) {
    if (cycle == null) {
      return false;
    }

    return Long.toString(cycle.getUserId()).equals(authentication.getName());
  }
}

但现在,我正在尝试对记录实施类似的安全检查。因此,在获取给定循环的记录时,我需要检查循环的 userId 是否与身份验证对象中的 id 匹配。

我的 RecordRepository 上有以下方法:

@Repository
public interface RecordRepository extends JpaRepository<Record, Long> {

      @PreAuthorize("hasRole('ROLE_ADMIN') OR @securityCheck.check(???, authentication)")
      Page<Record> findByCycle_Id(@Param("id") Long id, Pageable pageable);
}

是否可以使用我在此 securityCheck 中使用此方法查询的 id 访问循环内的 userId?如果不是,那么实现此功能的正确 Spring 方法是什么?

对不起,我的问题不清楚。如果需要进一步解释,请告诉我。

编辑: 我通过访问后过滤器中的返回页面找到了快速而肮脏的解决方案。缺点是当返回的数组为空时,我可以访问不属于我的登录用户的记录(所以我仍在寻找更优雅的解决方案)

@PostAuthorize("hasRole('ROLE_ADMIN') OR @securityCheck.check(returnObject, authentication)")
Page<Record> findByCycle_Id(@Param("id") Long id, Pageable pageable);

public boolean check(Page<Record> page, Authentication authentication) {
    log.debug("===Security check for page===");
    if (!page.hasContent()) {
      return true;
    }

    long userId = page.getContent().get(0).getCycle().getUserId();

    return Long.toString(userId).equals(authentication.getName());
  }

【问题讨论】:

  • 你的项目对我来说很有趣。是在github上吗?不幸的是,我没有这个问题的答案,但我很想在当天晚些时候看看。当我需要自定义太多时,我总是放弃使用 Spring Data Rest,因为它对我来说更像是一种负担,而不是一个支持框架。也许你的项目可以改变我的想法并向我展示新的想法:)
  • 不幸的是,这个特定项目不是开源的,但如果你想给我发消息,我很乐意分享我的解决方案 :) 我为我的问题找到了一个肮脏的解决方案,但我仍然希望还有更多有弹性的方式(见我更新的问题)......

标签: java spring-boot spring-data-rest


【解决方案1】:

您可以在您的应用程序中引入两个处理程序,如下所示:-

  //This is your Request handler

public class CxfHttpRequestHandler extends AbstractPhaseInterceptor<Message> {


    public CxfHttpRequestHandler() {
        super("marshal");
    }

    @Override
    public void handleMessage(final Message message) throws Fault {
        //handle your all request here
       }
}

//this is your Response handler
public class CxfHttpResponseHandler extends AbstractPhaseInterceptor<Message> {

    public CxfHttpResponseHandler() {
        super("pre-stream");
    }

    @Override
    public void handleMessage(final Message message) throws Fault {
        //handle your outgoing message here
    }
}


Add the following  in your spring-bean.xml file:-

<bean id="cxfHttpRequestHandler" class="com.yourpackage.CxfHttpRequestHandler" >
        <description>
            This Bean implements Interceptor for all request
        </description>
</bean>

<bean id="cxfHttpResponseHandler" class="com.yourpackage.CxfHttpResponseHandler">
        <description>
            This Bean implements Interceptor for all response
        </description>
    </bean>

<cxf:bus>
        <cxf:inInterceptors>
            <ref bean="cxfHttpRequestHandler" />
        </cxf:inInterceptors>

        <cxf:outInterceptors>
            <ref bean="cxfHttpResponseHandler" />
        </cxf:outInterceptors>

</cxf:bus>

【讨论】:

    【解决方案2】:

    如果您只需要在检查 userId 与您登录的用户相同的同时获取“Cycle”的特定记录,我会使用类似的内容:

    Page<Record> findByCycle_IdAndUserId(Long cycleId, Long userId, Pageable pageable);
    

    JPA 可以自己处理很多事情,而无需您手动创建查询。查询方法见https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods

    当然,这意味着需要在控制器端使用 Spring Security 而不是模型端来处理安全检查。你的 DAO 不必关心请求是否有效/授权,因为这些检查是事先完成的。

    干杯

    【讨论】:

      【解决方案3】:

      如果我没听错的话……

      首先,确保SpEL EvaluationContext extension 已启用。

      然后做这样的事情:

      public interface RecordRepository extends JpaRepository<Record, Long> {
      
          @Query("select r from Record r join r.cycle c where c.id = ?1 and (c.userId = ?#{@RecordRepository.toLong(principal)} or 1 = ?#{hasRole('ROLE_ADMIN') ? 1 : 0})")
          Page<Record> findByCycleId(Long id, Pageable pageable);
      
          default Long toLong(String name) {
              return Long.valueOf(name);
          }
      }
      

      我认为principal 包含userId 的字符串表示形式,所以在这里我将其转换为 long 然后比较它们...

      您也可以查看我的example 与此问题相关...

      更新

      尝试使用 T(java.lang.Long).valueOf(principal) 并从您的存储库中删除 toLong 方法,而不是 SpEL 表达式中的 @RecordRepository.toLong(principal)

      【讨论】:

      • 感谢您的回答。我一定会试一试的。然而,我希望有一些解决方案不需要在代码中使用自定义查询和选择。这看起来不太“有弹性”
      • @Smajl 正如您在 Spring 文档中看到的 - 当问题与记录级安全性有关时,在查询中使用 Spring Security SpEL 函数是一种“有弹性”的方式。
      猜你喜欢
      • 2018-05-28
      • 2018-06-03
      • 1970-01-01
      • 2017-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-18
      • 2012-09-25
      相关资源
      最近更新 更多