简体   繁体   English

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

[英]Spring Boot - make sure data belongs to current logged in user

I have a Spring Boot REST API that I'm building.我有一个正在构建的 Spring 引导 REST API。 Im slightly stuck on the correct way to design my API in a way that protects each individual users' data.我有点坚持以保护每个用户数据的方式设计我的 API 的正确方法。 For example, consider the following database relations:例如,考虑以下数据库关系:

User -> (Has Many) Projects -> (Has Many) Tasks.用户->(有很多)项目->(有很多)任务。 (A User has-many Projects, and a Project has-many tasks). (一个用户有很多项目,一个项目有很多任务)。

For example, if I design my endpoints in the following way:例如,如果我按以下方式设计端点:

GET /api/v1/projects/{projectId}

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

Just as a simple example for the above, how can I make sure, when creating new tasks for a certain project, that the project belongs to the logged in user?就像上面的一个简单示例,在为某个项目创建新任务时,如何确保该项目属于登录用户?

Currently, I am using JWT tokens via Spring Security as my authentication strategy, and included in the payload of the token I have my Users' id.目前,我通过 Spring Security 使用 JWT 令牌作为我的身份验证策略,并包含在我拥有用户 ID 的令牌的有效负载中。 So with every request I can retrieve the user, but surely that's incredibly inefficient to be making so many requests to the database and check if the user actually has a given project.因此,对于每个请求,我都可以检索用户,但是向数据库发出如此多的请求并检查用户是否真的有给定的项目,这肯定是非常低效的。

Some solution I was thinking about is to simply have endpoints designed like this:我正在考虑的一些解决方案是简单地设计如下端点:

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

And then I can use the user id in the JWT payload and compare it to the user id in the request parameter.然后我可以使用 JWT 有效负载中的用户 ID,并将其与请求参数中的用户 ID 进行比较。 But then that would mean with every new relation in my database, the length of the url is going to be massive:) Also I guess it would mean all the business logic would be inside the User service for the whole application, right?但这意味着对于我的数据库中的每个新关系,url 的长度将会很大:) 我猜这意味着所有业务逻辑都将在整个应用程序的用户服务中,对吧? Which seems a little odd to me... but maybe I'm wrong.这对我来说似乎有点奇怪......但也许我错了。

Im not sure if thats an issue or not, but just trying to design the API to be as elegant as possible.我不确定这是否是一个问题,但只是试图将 API 设计得尽可能优雅。

Thanks again!再次感谢!

Checking if the user has permissions to a project on every request is the correct solution.检查用户是否对每个请求都具有项目权限是正确的解决方案。 Consider cases when many other applications / users are calling your API.考虑许多其他应用程序/用户正在调用您的 API 的情况。 You want to make sure that your API is as secure as possible and cannot be manipulated from the frontend.您要确保您的 API 尽可能安全,并且不能从前端进行操作。

To make it more efficient you should have a way/query to check associations in your database like a simple query that returns true/false which should be quicker than retrieving all the data and comparing in Java code.为了提高效率,您应该有一种方法/查询来检查数据库中的关联,例如返回 true/false 的简单查询,这应该比检索所有数据并在 Java 代码中进行比较更快。

And when possible combine multiple database queries into one, like for one of your examples:并在可能的情况下将多个数据库查询合并为一个,例如您的示例之一:

GET /api/v1/projects/{projectId}

in this case, don't run a query to get a user's information and a query for the requested project.在这种情况下,不要运行查询来获取用户信息和请求项目的查询。 Instead you could do a single query with a join between the user's table and the project table which should only return a project if the user is associated with it.相反,您可以通过用户表和项目表之间的连接来执行单个查询,如果用户与之关联,则该表只应返回项目。 The best way really depends on how your database is structured.最好的方法实际上取决于您的数据库的结构。

Adding a user id into the API URL is just redundant information.在 API URL 中添加用户 ID 只是多余的信息。 Just because the user id in the token matches the user id in the URL doesn't mean the user has any kind of permissions to any project.仅仅因为令牌中的用户 id 与 URL 中的用户 id 匹配,并不意味着用户对任何项目具有任何类型的权限。

Another solution to be avoided is to include the user's project ids in the JWT token which you can then compare without making a database request.另一个要避免的解决方案是将用户的项目 ID 包含在 JWT 令牌中,然后您可以在不发出数据库请求的情况下进行比较。 This is bad for several reasons:这很糟糕,有几个原因:

  1. The token should only have required information for the user to access the API, it shouldn't have business logic令牌应该只有用户访问 API 所需的信息,它不应该有业务逻辑
  2. Depending on how much business logic you store in the token the token can become large in size.根据您在令牌中存储多少业务逻辑,令牌可能会变大。 See this post for a discussion on size limits: What is the maximum size of JWT token?有关大小限制的讨论,请参阅此帖子: JWT 令牌的最大大小是多少?
  3. If there is a way for the someone other than the user (like admin) to add/remove a user's association to a project then that change will not be reflected in the token until the token's data is refreshed如果用户以外的其他人(如管理员)有办法添加/删除用户与项目的关联,那么在刷新令牌数据之前,该更改不会反映在令牌中

EDIT:编辑:

On the spring side I have used the @PreAuthorize annotation before to handle these types of method checks.在 spring 方面,我之前使用过@PreAuthorize注释来处理这些类型的方法检查。 Below is pseudo code as an example.下面以伪代码为例。

@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;
    }
}

The annotation and the security service can then be applied to multiple methods.然后可以将注释和安全服务应用于多个方法。 You can have many different security services depending on what your are checking.根据您检查的内容,您可以拥有许多不同的安全服务。

There are other ways available too https://www.baeldung.com/spring-security-method-security and this has to be enabled in spring's configuration (also explained in the link).还有其他可用的方法https://www.baeldung.com/spring-security-method-security必须在 spring 的配置中启用(也在链接中解释)。

Hi so if I understood it correctly you want to automatically assign the task that is going to be created with "POST /api/v1/projects/{projectId}/tasks" to the current logged in user.您好,如果我理解正确,您希望自动将使用“POST /api/v1/projects/{projectId}/tasks”创建的任务分配给当前登录的用户。

You could try to add a Parameter 'Principal principal' to your rest controller.您可以尝试将参数“Principal principal”添加到 rest controller。 The Principal is the user that is sending the request.委托人是发送请求的用户。

After you have your Prinicipal, you could write a simple convert method(for example: convertPrincipalToUser(Principal principal) which returns you the user. Finally you can add your user to the corresponding task)获得委托人后,您可以编写一个简单的转换方法(例如:convertPrincipalToUser(Principal principal) 返回用户。最后您可以将用户添加到相应的任务中)

Here is some more information about it: https://www.baeldung.com/get-user-in-spring-security以下是有关它的更多信息: https://www.baeldung.com/get-user-in-spring-security

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM