首页 > 解决方案 > Spring Boot - 确保数据属于当前登录用户

问题描述

我有一个正在构建的 Spring Boot REST API。我有点坚持以保护每个用户数据的方式设计我的 API 的正确方法。例如,考虑以下数据库关系:

用户->(有很多)项目->(有很多)任务。(一个用户有很多项目,一个项目有很多任务)。

例如,如果我按以下方式设计端点:

GET /api/v1/projects/{projectId}

POST /api/v1/projects/{projectId}/tasks

就像上面的一个简单示例,在为某个项目创建新任务时,如何确保该项目属于登录用户?

目前,我通过 Spring Security 使用 JWT 令牌作为我的身份验证策略,并包含在我拥有用户 ID 的令牌的有效负载中。因此,对于每个请求,我都可以检索用户,但是向数据库发出如此多的请求并检查用户是否真的有给定的项目,这肯定是非常低效的。

我正在考虑的一些解决方案是简单地设计如下端点:

/api/v1/users/{userId}/projects/{projectId}/tasks

然后我可以使用 JWT 有效负载中的用户 ID,并将其与请求参数中的用户 ID 进行比较。但这意味着对于我数据库中的每个新关系,url 的长度都会很大:) 另外我猜这意味着所有业务逻辑都将在整个应用程序的用户服务中,对吧?这对我来说似乎有点奇怪......但也许我错了。

我不确定这是否是一个问题,但只是试图将 API 设计得尽可能优雅。

再次感谢!

标签: javaspringspring-bootspring-security

解决方案


检查用户是否对每个请求都具有项目权限是正确的解决方案。考虑许多其他应用程序/用户正在调用您的 API 的情况。您希望确保您的 API 尽可能安全,并且不能从前端进行操作。

为了提高效率,您应该有一种方法/查询来检查数据库中的关联,例如返回 true/false 的简单查询,这应该比检索所有数据并在 Java 代码中进行比较更快。

并在可能的情况下将多个数据库查询合并为一个,例如您的示例之一:

GET /api/v1/projects/{projectId}

在这种情况下,不要运行查询来获取用户信息和请求项目的查询。相反,您可以通过用户表和项目表之间的连接来执行单个查询,如果用户与之关联,则该表只应返回项目。最好的方法实际上取决于您的数据库的结构。

在 API URL 中添加用户 ID 只是多余的信息。仅仅因为令牌中的用户 id 与 URL 中的用户 id 匹配并不意味着用户对任何项目具有任何类型的权限。

另一个要避免的解决方案是在 JWT 令牌中包含用户的项目 ID,然后您可以在不发出数据库请求的情况下进行比较。这很糟糕,有几个原因:

  1. 令牌应该只有用户访问 API 所需的信息,它不应该有业务逻辑
  2. 根据您在令牌中存储多少业务逻辑,令牌可能会变大。有关大小限制的讨论,请参阅此帖子:JWT 令牌的最大大小是多少?
  3. 如果用户以外的其他人(如管理员)有办法添加/删除用户与项目的关联,那么在刷新令牌数据之前,该更改不会反映在令牌中

编辑:

在弹簧方面,我之前使用过@PreAuthorize注释来处理这些类型的方法检查。下面以伪代码为例。

@RestController
public class MyController {

    @PostMapping
    @PreAuthorize("@mySecurityService.isAllowed(principal, #in)")
    public SomeResponseType api1(SomeRequestType requestData) {
        /* this is not reached unless mySecurityService.isAllowed
        returns true, instead a user gets a 401/403 HTTP response 
        code (i don't remember the exact one) */
    }
}

@Service
public class MySecurityService {

    /*
    Object principal - this is spring's UserDetails object that is
    returned from the AuthenticationProvider. So basically a Java
    representation of the  JWT token which should have the 
    user's username.

    SomeRequestType requestData - this is the request data that was 
    sent to the API. I'm sure there is a way to get the project ID 
    from the URL here as well.
    */
    public boolean isAllowed(Object principal, SomeRequestType requestData) {
        /*
        take the user's username from the principal, take the 
        project ID from the request data and query the database 
        to check authorization, return true if authorized

        make this check efficient
        */

        return false;
    }
}

然后可以将注释和安全服务应用于多个方法。根据您检查的内容,您可以拥有许多不同的安全服务。

还有其他可用的方法https://www.baeldung.com/spring-security-method-security必须在 spring 的配置中启用(也在链接中解释)。


推荐阅读