如何用自签发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
相关产品推荐
相关产品推荐

