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 - 确保数据属于当前登录用户的主要内容,如果未能解决你的问题,请参考以下文章
使用 MyBatis + H2 配置 Spring Boot 身份验证/登录
Spring Boot + Web Socket 实现扫码登录,这种方式太香了!!
如何使用 Keycloak 在 Spring Boot 中获取当前登录用户?