Spring Boot - 确保数据属于当前登录用户

Posted

技术标签:

【中文标题】Spring Boot - 确保数据属于当前登录用户【英文标题】:Spring Boot - make sure data belongs to current logged in user 【发布时间】:2021-08-29 05:43:33 【问题描述】:

我正在构建一个 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 设计得尽可能优雅。

再次感谢!

【问题讨论】:

【参考方案1】:

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

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

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

GET /api/v1/projects/projectId

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

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

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

    令牌应仅包含用户访问 API 所需的信息,不应包含业务逻辑 根据您在令牌中存储多少业务逻辑,令牌的大小可能会变大。有关大小限制的讨论,请参阅此帖子:What is the maximum size of JWT token? 如果用户以外的其他人(如管理员)有办法添加/删除用户与项目的关联,则在刷新令牌数据之前,该更改不会反映在令牌中

编辑:

在 spring 方面,我之前使用了@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 的配置中启用(也在链接中解释)。

【讨论】:

有一些“春天”的方法吗?还是我应该为每种方法手动查询服务中的用户项目?如果您能提供某种代码示例,那就太好了,我有点困惑这在实践中的样子。非常感谢! @randomdev 我添加了一个小例子,说明可以做到这一点【参考方案2】:

您好,如果我理解正确,您希望自动将使用“POST /api/v1/projects/projectId/tasks”创建的任务分配给当前登录的用户。

您可以尝试将参数“Principal principal”添加到您的休息控制器。 Principal 是发送请求的用户。

当你有了你的委托人后,你可以写一个简单的转换方法(例如:convertPrincipalToUser(Principal principal) 它返回你的用户。最后你可以将你的用户添加到相应的任务中)

以下是有关它的更多信息: https://www.baeldung.com/get-user-in-spring-security

【讨论】:

嗯,不仅分配,还要检查他是否有权查看。例如 GET 请求也是如此。谢谢,我想看看控制器中可用的 Principal :) 主体有一个方法 principal.getName();通过这个,您可以通过在回答中提到的转换来获得您的用户。一个建议是编写一个 user.hasAccess(...) 方法。

以上是关于Spring Boot - 确保数据属于当前登录用户的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring 检查实体/对象是不是属于当前登录的用户

使用 MyBatis + H2 配置 Spring Boot 身份验证/登录

Spring Boot + Web Socket 实现扫码登录,这种方式太香了!!

如何使用 Keycloak 在 Spring Boot 中获取当前登录用户?

Spring Boot + Web Socket 实现扫码登录,这种方式太香了!!

如何使用 Spring Boot Thymeleaf 显示当前登录的用户?