如何配置 Spring-Data-Rest 以仅返回登录用户拥有的数据

Posted

技术标签:

【中文标题】如何配置 Spring-Data-Rest 以仅返回登录用户拥有的数据【英文标题】:How to configure Spring-Data-Rest in way to only return data owned by the logged in user 【发布时间】:2020-04-28 22:24:01 【问题描述】:

我正在开发一个 Spring Boot RESTful 应用程序,该应用程序将为 Web 应用程序公开一堆 API 以对资源执行 CRUD 操作。

我正在使用spring-data-rest(当然还有spring-data-jpa)在Spring Magic的帮助下公开实体/存储库。

即使我使用 spring-security 保护(基于角色)端点,它也不是完全安全的。

例如: 我有一个User 实体与one-to-manyCar 的关系。所以获取用户汽车的端点(由spring-data-rest自动暴露)是localhost:8080/users/userId/cars

但是,任何具有所需角色的用户都可以只传递另一个用户的userId 并仍然访问端点。 我想要的行为是以某种方式保护这些端点,如果我登录用户的 ID 是1,那么我们只能点击localhost:8080/users/1/cars。与任何其他 userId 的任何其他请求都应该以 403 或其他形式结束。

注意:我知道如果编写自己的控制器,那么我可以获得主体的句柄并做我想做的事。我只是想知道spring-data-rest 中有没有办法或模式来实现这一点?

【问题讨论】:

你需要为此编写拦截器 @VinayHegde 能否请您详细说明一下或指出正确的方向? 您将userId 作为路径变量似乎很不寻常。为什么不使用登录用户的 id?另请参阅 Spring Data & Spring Security Configuration 在 JPA 查询中使用登录用户的 id。 @Ritesh 该端点不是自定义控制器,它已被 spring-data-rest 自动公开。 我会推荐存储库自定义。有关使用 @RepositoryRestResourceSecurityEvaluationContextExtension 的示例,请参阅 Spring Data REST filtering data based on the user。 【参考方案1】:

由于您已经使用 Spring Security 保护了应用程序,因此这里是 Method Security Expressions 的另一种选择

请查看@Pre and @Post Annotations 了解您的要求。

    您可以将登录用户的userId存储到Authentication对象中。详情here。

    使用@PreAuthorize 注解保护所需的方法,如下所示

    @PreAuthorize("#user.userId == authentication.principal.userId")
    public List<Car> getCars(User user)..
    

记得开启方法安全

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter ..

【讨论】:

这不能解决问题。问题要求根据当前登录的用户(主体)“过滤”结果。可能的(注意性能)解决方案也可以在这里找到***.com/a/30877376/11152683 @Lubo 我找不到您评论中提到的“过滤”要求。要求是“我想要的行为是以某种方式保护这些端点,如果我登录用户的 ID 为 1,那么我们只能点击 localhost:8080/users/1/cars。 " 有要求,但它是隐含的...根据给定的 URL,soumitri-pattnaik 确实希望只获得与给定用户关联的汽车...另一方面,你是对的.利用基于路径的安全性可以实现相同的行为。这比死的简单。看看这里:***.com/a/27649847/11152683【参考方案2】:

您正在使用 spring security(太棒了:D),因此最好创建一个简单的过滤器,注册它,然后在该过滤器中进行自定义授权。

简介

    创建自定义过滤器

    从 URL 路径获取 userId

    从 SecurityContextHolder 获取 userId(经过身份验证的用户主体)

    比较获取的用户 ID

    在 spring security config 中注册过滤器(在 BasicAuthenticationFilter 之后)

1- 创建自定义过滤器(伪代码)

public class CustomFilter extends GenericFilterBean 

@Override
public void doFilter(
        ServletRequest request,
        ServletResponse response,
        FilterChain chain) throws IOException, ServletException 

    //Fetch userId from path 
    HttpServletRequest req = (HttpServletRequest) request;
    String path = req.getContextPath();
    //..


    //Fetch userId from SecurityContextHolder (User Principal)
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    User user = (User) authentication.getPrincipal();
    Long userId = user.getId();


    //Compare userId (fethced from path) with authenticated userId (fetched from principal)
    //If comparison is ok
    chain.doFilter(request, response);


    //else
    //throw Unauthorize


2- 在spring security config中注册一个过滤器(在BasicAuthenticationFilter.class之后)

@Configuration
public class Configuration extends WebSecurityConfigurerAdapter 

@Override
protected void configure(HttpSecurity http) throws Exception 
    http.addFilterAfter(
            new CustomFilter(), BasicAuthenticationFilter.class);


有了这个结构,当一个经过身份验证的用户发送请求时,请求会先被检查(userIds之间的比较)然后发送。

更多关于在spring security中创建过滤器的信息:

A Custom Filter in the Spring Security Filter Chain

【讨论】:

太复杂了。看看 PostFilter、PreAuthorize、Query...注意,Query 可以使用 SPEL 的东西,它确实可以访问 UserPrincipal...【参考方案3】:

要实现这一点,你需要写一个Interceptor。它将在以下情况下使用:

    在向控制器发送请求之前

    在向客户端发送响应之前

在编写任何Interceptor 之前,它应该实现HandlerInterceptor 接口。 Interceptor 支持的三种方法是:

    preHandle() 方法 - 在发送请求之前执行操作 到controller。此方法应返回 true 以返回 回复客户。 postHandle() 方法 - 用于在发送前执行操作 对客户的回应。 afterCompletion() 方法 - 用于执行操作 在完成请求和响应之后。

代码:

    @Component
    public class MyInterceptor implements HandlerInterceptor 
       @Override
       public boolean preHandle(
          HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 

       String pathVariablesMap = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); 
      //From this pathVariablesMap  extract UserId and match with a loggedinUserId
       
       @Override
       public void postHandle(
          HttpServletRequest request, HttpServletResponse response, Object handler, 
         ) throws Exception 

       @Override
       public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
          Object handler, Exception exception) throws Exception 
    

通过使用InterceptorRegistry,您可以注册您的Interceptors,如下所示:

@Component
public class MyRegistoryConfig extends WebMvcConfigurer
   @Autowired
   MyInterceptor myInterceptor ;

   @Override
   public void addInterceptors(InterceptorRegistry registry) 
      registry.addInterceptor(myInterceptor );
   

更多信息请点击此链接Interceptors

编辑:正如@Ritesh 建议的那样,添加了这一点。

【讨论】:

OP 需要验证 userId 路径变量的值是否与登录用户的 id 匹配,而不是检查标头。在拦截器的 preHandle 方法中执行的一种方法是获取路径变量映射 - 类似于 Map pathVariablesMap = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); - 并从该映射中提取 userId 键的值并将其与登录用户的 id 进行比较。

以上是关于如何配置 Spring-Data-Rest 以仅返回登录用户拥有的数据的主要内容,如果未能解决你的问题,请参考以下文章

HAL浏览器无法在spring-data-rest中正确自动配置

您如何配置 Xcode Server (Bot) 以仅保留 n 个最近的集成?

如何配置 Cups Linux 服务器以仅允许从特定 IP 打印?

如何配置 WildFly 8.2.0 日志记录以仅在调试级别显示应用程序

如何将 spring-data-rest 与 spring websocket 混合到一个实现中

如何在 spring-data-rest 中将 Page<ObjectEntity> 映射到 Page<ObjectDTO>