You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用自签发JWT防范IDOR?多租户Spring应用授权方案

多租户Spring应用的JWT授权校验优化方案

问题背景

我正在研读Spring官方文档中关于HttpServletRequest和JWT的授权内容。我的应用采用多租户架构,需要为每个用户校验其所属租户,以此决定是否允许请求。

补充说明

此前遗漏了一点:有其他微服务需要调用该API并访问所有租户与用户,因此路径变量{tenantId}和{userId}是必需的,无法通过提取JWT声明来替代。

示例场景

我有一个端点/api/v1/tenants/{tenantId}/users/{userId}/settings,需要确保租户1的用户123仅能对自己的设置进行CRUD操作——既不能访问同租户其他用户的设置,也不能访问其他租户用户的设置。

现有了解的Spring授权配置方式

我知道JWT资源服务器可配置特定授权校验,比如Spring官方文档中的示例:

全局配置方式

@Configuration
@EnableWebSecurity
public class DirectlyConfiguredJwkSetUri {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/contacts/**").access(hasScope("contacts"))
                .requestMatchers("/messages/**").access(hasScope("messages"))
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(Customizer.withDefaults())
            );
        return http.build();
    }
}

方法级配置方式

@PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages(...) {}

曾尝试的方案及问题

我曾考虑在签发JWT时为用户添加SCOPE_TENANT:{tenantId}权限,并编写如下代码(用户名为邮箱,无法使用@PreAuthorize("#userId == authentication.name")):

@PreAuthorize("hasAuthority('TENANT:' + #tenantId) or hasAuthority('ADMIN')")
@GetMapping("{tenantId}/users/{userId}/settings")
public ResponseEntity<Settings> getTenantResources(@PathVariable long tenantId,
@PathVariable long userId, Authentication authentication) {
    User user = (User) authentication.getPrincipal();
    if (user.getId != userId) {
        throw new IdConflicException();
    }
    // 从数据库中获取并返回用户设置
}

但该方案存在大量代码重复,易出错且难以维护,我希望找到更优方案,比如使用过滤器或决策器来完成这些校验。

当前采用的方案

经过相关解答与建议,我目前采用了不涉及方法级注解的方案——编写自定义工具类从已认证用户的JWT中提取tenantId和userId声明,并校验路径变量与声明是否匹配,然后在每个端点中手动调用该方法:

public static void checkAllowedToAccessTenantAndUser(long tenantId, long userId, Jwt jwt) {
    long jwtUserId = jwt.getClaim("userId");
    long jwtTenantId = jwt.getClaim("tenantId");

    if (scope.contains(Authorities.CLIENT.getAuthority())) {
        return;
    }

    if (jwtUserId != userId || jwtTenantId != tenantId) {
        throw new IdorException();
    }
}

内容的提问来源于stack exchange,提问作者GeekChap

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:39:50