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

在Spring Authorization Server中为AccessToken添加租户信息

问题解答

1. 在JwtCustomizer中获取当前认证用户并添加租户角色Claims

你可以通过JwtEncodingContext.getPrincipal()拿到当前的Authentication对象,从中提取用户标识(比如用户ID),再查询数据库获取该用户关联的所有租户及对应角色,最后把这些信息写入JWT的Claims中。

具体实现步骤:

  • 注入你的用户/租户数据访问服务(比如UserTenantService)
  • 在jwtCustomizer中提取认证用户的核心信息
  • 查询用户对应的租户角色数据并构造指定格式
  • 将数据添加到JWT Claims中

示例代码:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer(UserTenantService userTenantService) {
    return context -> {
        if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
            // 获取当前认证用户的Authentication对象
            Authentication authentication = context.getPrincipal();
            // 假设你的用户信息封装在自定义的UserDetails实现里
            CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
            
            // 查询该用户关联的所有租户与角色
            List<TenantRoleDto> tenantRoles = userTenantService.getTenantRolesByUserId(user.getId());
            
            // 将租户角色数据写入JWT Claims
            context.getClaims().claim("tenants", tenantRoles);
        }
    };
}

注意:要确保你的CustomUserDetails包含用户ID等查询所需字段,UserTenantService能正确从数据库读取用户-租户-角色的关联关系。

2. 给/token接口传入tenantId,返回仅该租户的AccessToken

这种方案完全可行,核心是在Token请求阶段接收tenantId参数,然后只将该租户的角色信息写入JWT。需要做以下改造:

步骤1:扩展Token请求参数,接收tenantId

自定义OAuth2TokenRequestConverter,把请求中的tenantId参数提取出来,存入Authentication的details中:

@Component
public class CustomTokenRequestConverter extends OAuth2AuthorizationCodeGrantRequestConverter {
    @Override
    public OAuth2AuthorizationCodeGrantRequest convert(HttpServletRequest request) {
        OAuth2AuthorizationCodeGrantRequest grantRequest = super.convert(request);
        String tenantId = request.getParameter("tenantId");
        
        if (tenantId != null) {
            // 将tenantId存入Authentication的details字段
            Authentication authentication = grantRequest.getAuthentication();
            Map<String, Object> details = new HashMap<>();
            details.putAll(authentication.getDetails() != null ? (Map) authentication.getDetails() : Collections.emptyMap());
            details.put("tenantId", tenantId);
            
            // 构造携带tenantId的新Authentication对象
            Authentication newAuth = new UsernamePasswordAuthenticationToken(
                    authentication.getPrincipal(),
                    authentication.getCredentials(),
                    authentication.getAuthorities()
            );
            newAuth.setDetails(details);
            
            // 返回更新后的授权请求
            return new OAuth2AuthorizationCodeGrantRequest(
                    grantRequest.getClientRegistration(),
                    grantRequest.getAuthorizationExchange(),
                    newAuth
            );
        }
        return grantRequest;
    }
}

步骤2:在JwtCustomizer中获取tenantId并筛选角色

修改之前的jwtCustomizer,从Authentication的details中取出tenantId,仅查询该租户的角色信息:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer(UserTenantService userTenantService) {
    return context -> {
        if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
            Authentication authentication = context.getPrincipal();
            CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
            Map<String, Object> details = (Map<String, Object>) authentication.getDetails();
            
            List<TenantRoleDto> tenantRoles;
            if (details.containsKey("tenantId")) {
                String tenantId = (String) details.get("tenantId");
                // 仅查询指定租户的角色信息
                tenantRoles = Collections.singletonList(userTenantService.getTenantRoleByUserIdAndTenantId(user.getId(), tenantId));
            } else {
                // 未传tenantId时返回所有租户角色
                tenantRoles = userTenantService.getTenantRolesByUserId(user.getId());
            }
            
            context.getClaims().claim("tenants", tenantRoles);
        }
    };
}

两种方案对比

  • 传tenantId到/token接口:用户每次只能绑定一个租户,每个租户对应独立AccessToken,后续请求无需携带租户标识,适合用户固定在单个租户下操作的场景。
  • 请求头携带tenantId:单个AccessToken支持跨租户操作,灵活性更高,但每次请求都需携带租户标识,适合用户频繁切换租户的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:16:30