如何配置 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-many
与Car
的关系。所以获取用户汽车的端点(由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 自动公开。
我会推荐存储库自定义。有关使用 @RepositoryRestResource
和 SecurityEvaluationContextExtension
的示例,请参阅 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>