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

Spring Security:已登录用户切换租户生成新AccessToken方案问询

多租户系统租户切换实现方案

核心思路

利用已登录用户的有效JWT作为身份凭证,向认证服务器发起租户切换请求,携带当前JWT和目标tenantId。认证服务器验证JWT有效性、用户与目标租户的关联权限后,生成包含新tenantId的新Access Token返回给客户端,无需用户再次输入密码。


具体实现步骤

1. 后端新增租户切换API(认证服务器端)

在认证服务器中新增接口处理租户切换请求:

@RestController
@RequestMapping("/auth")
public class TenantSwitchController {

    @Autowired
    private JwtDecoder jwtDecoder;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenGenerator jwtTokenGenerator; // 自定义JWT生成器,需自行实现

    @PostMapping("/switch-tenant")
    public ResponseEntity<AuthResponse> switchTenant(
            @RequestHeader("Authorization") String authHeader,
            @RequestParam String newTenantId) {

        // 1. 解析并验证当前JWT
        String token = authHeader.replace("Bearer ", "");
        Jwt jwt = jwtDecoder.decode(token);
        String username = jwt.getSubject();

        // 2. 加载用户详情,验证用户是否有权访问新租户
        SpringSecurityUserWithAdditionalData user = (SpringSecurityUserWithAdditionalData) userDetailsService.loadUserByUsername(username);
        if (!user.getTenantIds().contains(newTenantId)) { // 假设用户实体维护了可访问租户ID集合
            throw new AccessDeniedException("无权限访问该租户");
        }

        // 3. 生成包含新tenantId的新JWT
        Map<String, Object> extraClaims = new HashMap<>();
        extraClaims.put("tenantId", newTenantId);
        extraClaims.put(GlobalConstants.JWT_CLAIM_ROLA_LIST, user.getAuthorities()); // 保留原有权限
        String newToken = jwtTokenGenerator.generateToken(user, extraClaims);

        // 4. 返回新Token
        return ResponseEntity.ok(new AuthResponse(newToken));
    }
}

2. 自定义JWT生成器实现

封装JWT生成逻辑:

@Component
public class JwtTokenGenerator {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private long expirationMs;

    public String generateToken(UserDetails userDetails, Map<String, Object> extraClaims) {
        return Jwts.builder()
                .setClaims(extraClaims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expirationMs))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
}

3. 调整Security配置,放行切换接口

在WebSecurityConfiguration的defaultSecurityFilterChain中,允许已认证用户访问切换接口:

// @formatter:off 
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        ...
        .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/changeTenant").permitAll()
                .requestMatchers("/auth/switch-tenant").authenticated() // 仅允许已认证用户访问
                .anyRequest().authenticated())
        .and()
            .logout()
            .clearAuthentication(true)
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID")
            .logoutSuccessHandler(new CustomLogoutSuccessHandler())
        .and()
            .formLogin()
            .loginPage("/login")
            .permitAll().and();
    return http.build();
}
// @formatter:on

4. 前端客户端实现

用户选择目标租户后,发起切换请求并更新本地Token:

async function switchTenant(newTenantId) {
    const currentToken = localStorage.getItem('accessToken');
    const response = await fetch('/auth/switch-tenant', {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${currentToken}`,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: `newTenantId=${newTenantId}`
    });

    if (response.ok) {
        const data = await response.json();
        localStorage.setItem('accessToken', data.token);
        // 刷新页面或重新加载用户权限、菜单等数据
        window.location.reload();
    } else {
        alert('切换租户失败:' + await response.text());
    }
}

5. 扩展用户实体与UserDetails

确保SpringSecurityUserWithAdditionalData包含用户可访问的租户ID集合:

public class SpringSecurityUserWithAdditionalData extends User {

    private Set<String> tenantIds; // 用户可访问的租户ID集合

    // 构造函数、getter/setter
    public SpringSecurityUserWithAdditionalData(String username, String password, Collection<? extends GrantedAuthority> authorities, Set<String> tenantIds) {
        super(username, password, authorities);
        this.tenantIds = tenantIds;
    }

    public Set<String> getTenantIds() {
        return tenantIds;
    }
}

关键注意事项

  • Token有效性验证:必须校验当前JWT的签名、过期时间,拒绝非法请求。
  • 租户权限校验:严格验证用户与目标租户的关联关系,防止越权访问。
  • Token失效策略:原Token可选择继续有效或加入黑名单立即失效,需根据业务需求决定。
  • 会话同步:切换租户后,前端需更新本地Token并重新加载租户相关的用户数据。

内容的提问来源于stack exchange,提问作者Igor Milovanović

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 13:35:18