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

基于Spring Auth Server与Microsoft Entra ID实现用户模拟的方案咨询

解决方案建议

核心思路

RFC8693的Token Exchange默认依赖subject_token,但你仅持有被模拟用户的邮箱,因此需要扩展Spring Authorization Server的令牌交换能力,允许通过邮箱直接生成模拟令牌,或者先通过邮箱获取用户标识再完成交换。以下是两种可行方案:


方案一:自定义Token Exchange扩展(推荐)

直接扩展Spring Authorization Server的Token Exchange逻辑,支持用target_user_email参数替代subject_token,跳过获取被模拟用户令牌的步骤。

1. 更新客户端配置

确保react-client的授权类型包含令牌交换,并新增模拟权限的Scope:

spring.security.oauth2:
  authorizationserver:
    client:
      react-client:
        registration:
          authorization-grant-types:
            - authorization_code
            - refresh_token
            - urn:ietf:params:oauth:grant-type:token-exchange
          scopes:
            - openid
            - profile
            - https://graph.microsoft.com/User.ReadBasic.All
            - impersonate_user # 新增模拟权限Scope

2. 自定义令牌交换参数转换器

继承默认的TokenExchangeGrantAuthenticationConverter,添加对target_user_email的解析逻辑,通过Azure Graph API根据邮箱获取用户的sub(唯一标识符):

@Component
public class EmailAwareTokenExchangeConverter extends TokenExchangeGrantAuthenticationConverter {

    private final GraphServiceClient<Request> graphClient;

    public EmailAwareTokenExchangeConverter(GraphServiceClient<Request> graphClient) {
        this.graphClient = graphClient;
    }

    @Override
    protected Authentication extractSubject(OAuth2TokenExchangeGrantAuthenticationToken tokenExchangeToken) {
        // 优先尝试默认的subject_token解析逻辑
        Authentication subjectAuth = super.extractSubject(tokenExchangeToken);
        if (subjectAuth != null) {
            return subjectAuth;
        }

        // 若没有subject_token,读取target_user_email参数并查询用户信息
        String targetEmail = tokenExchangeToken.getAdditionalParameters().get("target_user_email").toString();
        User targetUser = graphClient.users()
                .buildRequest()
                .filter("mail eq '" + targetEmail + "'")
                .get()
                .getCurrentPage()
                .get(0);

        // 返回被模拟用户的认证信息
        return new UsernamePasswordAuthenticationToken(
                targetUser.getId(),
                null,
                Collections.emptyList()
        );
    }
}

3. 自定义令牌交换认证提供者

重写令牌生成逻辑,先验证管理员的模拟权限,再生成包含模拟标识的JWT:

@Component
public class ImpersonationTokenExchangeProvider extends TokenExchangeAuthenticationProvider {

    private final OAuth2TokenGenerator<?> tokenGenerator;

    public ImpersonationTokenExchangeProvider(OAuth2TokenGenerator<?> tokenGenerator) {
        super(tokenGenerator);
        this.tokenGenerator = tokenGenerator;
    }

    @Override
    protected OAuth2Token generateAccessToken(OAuth2TokenExchangeAuthenticationContext context) {
        OAuth2TokenExchangeAuthenticationToken authToken = context.getAuthentication();
        Authentication adminAuth = authToken.getPrincipal();

        // 验证管理员是否拥有模拟权限
        if (!adminAuth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_IMPERSONATE_USER"))) {
            throw new OAuth2AccessDeniedException("无用户模拟权限");
        }

        // 获取被模拟用户的认证信息,构建自定义Claims
        Authentication targetUserAuth = authToken.getSubject();
        Map<String, Object> claims = new HashMap<>();
        claims.put("sub", targetUserAuth.getName());
        claims.put("email", authToken.getAdditionalParameters().get("target_user_email"));
        claims.put("impersonated_by", adminAuth.getName()); // 添加管理员标识,用于审计

        // 生成模拟身份的JWT令牌
        OAuth2TokenContext tokenContext = OAuth2TokenContext.builder()
                .registeredClient(context.getRegisteredClient())
                .principal(targetUserAuth)
                .authorizationServerContext(context.getAuthorizationServerContext())
                .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                .claims(claims)
                .build();

        return this.tokenGenerator.generate(tokenContext);
    }
}

4. 注册自定义组件

在授权服务器配置中替换默认的转换器和提供者:

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig {

    @Bean
    public OAuth2AuthorizationServerConfigurer authorizationServerConfigurer(
            EmailAwareTokenExchangeConverter converter,
            ImpersonationTokenExchangeProvider provider) {
        return new OAuth2AuthorizationServerConfigurer()
                .tokenExchange(tokenExchange -> tokenExchange
                        .authenticationConverter(converter)
                        .authenticationProvider(provider));
    }
}

方案二:先通过Graph API获取用户令牌再交换

若希望严格遵循RFC8693规范,可让认证服务器先获取被模拟用户的令牌,再执行令牌交换:

1. 配置Graph API权限

在Azure Entra ID中给my-client注册应用添加User.Read.All和DelegatedPermissionGrant.ReadWrite.All权限,并完成管理员授权。

2. 获取被模拟用户令牌

认证服务器使用客户端凭据流获取Graph API令牌,再通过/users/{userId}/oauth2PermissionGrants接口(或ROPC流,不推荐)获取被模拟用户的访问令牌。

3. 执行Token Exchange

以管理员的令牌作为subject_token,被模拟用户的令牌作为target_token,调用令牌交换接口生成模拟令牌。


资源服务器侧验证

资源服务器可通过检查impersonated_by Claim识别模拟令牌,并记录审计日志:

@Component
public class ImpersonationTokenValidator implements OAuth2TokenValidator<Jwt> {

    @Override
    public OAuth2TokenValidatorResult validate(Jwt token) {
        if (token.getClaims().containsKey("impersonated_by")) {
            // 记录审计日志:管理员{impersonated_by}模拟用户{sub}执行操作
            return OAuth2TokenValidatorResult.success();
        }
        return OAuth2TokenValidatorResult.success();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:14:52