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

Bot Framework v3对接Teams实现SSO时登录后无验证码无法获取token问题

问题原因

该问题是Bot Framework v3 SDK已停止维护、未适配Teams渠道OAuth回调机制导致的,和你已完成的权限、AAD连接、manifest域名配置无关。v3自带的GetTokenDialog仅适配了WebChat渠道的验证码回传逻辑,没有处理Teams的登录回调上下文传递,所以登录完成后不会返回验证码。

修复步骤

  1. 替换原生GetTokenDialog组件
    不要使用SDK自带的登录对话框,手动构造带openUrl按钮的登录卡片,将Teams会话的conversationId、userId、serviceUrl编码后拼接到AAD授权链接的state参数中,AAD授权完成后会原封不动返回该参数用于会话定位。
  2. 自定义OAuth回调接口
    将AAD应用的重定向URI修改为你自行开发的回调接口,不要使用Bot Framework默认的回调地址。接口中完成三个动作:解析state参数拿到会话上下文、用授权码兑换访问令牌、主动调用Bot Connector API将验证码或令牌推送给对应的Teams用户,最后返回带window.close()脚本的响应自动关闭登录标签页。
  3. 补充验证码校验逻辑
    新增用户输入监听逻辑,校验用户输入的验证码匹配后,取出缓存的令牌直接走后续业务流程。
  4. 补全Teams manifest配置
    确认manifest中已添加webApplicationInfo节点,配置你的AAD应用ID和资源URI,这是Teams登录流程必需的配置。

代码示例

构造自定义登录卡片

private async Task SendCustomSignInCard(IDialogContext context)
{
    // 拼接会话上下文到state参数
    var state = $"conversationId={context.Activity.Conversation.Id}&userId={context.Activity.From.Id}&serviceUrl={Uri.EscapeDataString(context.Activity.ServiceUrl)}";
    // 构造AAD v2授权链接
    var authUrl = $"https://login.microsoftonline.com/{你的租户ID}/oauth2/v2.0/authorize?" +
        $"client_id={你的AAD客户端ID}" +
        $"&response_type=code" +
        $"&redirect_uri={Uri.EscapeDataString(你的回调接口地址)}" +
        $"&scope={Uri.EscapeDataString("你需要的Graph权限,比如User.Read openid offline_access")}" +
        $"&state={Uri.EscapeDataString(state)}";

    var signInCard = new HeroCard
    {
        Text = $"请登录{ConnectionName}以继续操作",
        Buttons = new List<CardAction>
        {
            new CardAction(ActionTypes.OpenUrl, "登录", value: authUrl)
        }
    };

    var reply = context.MakeMessage();
    reply.Attachments.Add(signInCard.ToAttachment());
    await context.PostAsync(reply);
    // 等待用户输入验证码
    context.Wait(HandleVerificationCodeInput);
}

OAuth回调接口处理逻辑

[HttpGet]
[Route("api/oauth/callback")]
public async Task<IActionResult> OAuthCallback(string code, string state)
{
    // 解析state参数获取会话信息
    var stateDict = HttpUtility.ParseQueryString(Uri.UnescapeDataString(state));
    var conversationId = stateDict["conversationId"];
    var userId = stateDict["userId"];
    var serviceUrl = stateDict["serviceUrl"];

    // 用授权码兑换访问令牌
    using var httpClient = new HttpClient();
    var tokenReqParams = new Dictionary<string, string>
    {
        {"client_id", 你的AAD客户端ID},
        {"client_secret", 你的AAD客户端密钥},
        {"code", code},
        {"grant_type", "authorization_code"},
        {"redirect_uri", 你的回调接口地址},
        {"scope", "你需要的Graph权限,和授权链接保持一致"}
    };
    var tokenResp = await httpClient.PostAsync(
        $"https://login.microsoftonline.com/{你的租户ID}/oauth2/v2.0/token", 
        new FormUrlEncodedContent(tokenReqParams));
    var tokenContent = await tokenResp.Content.ReadAsStringAsync();
    var tokenResult = JsonConvert.DeserializeObject<TokenResult>(tokenContent);

    // 生成6位随机验证码,和令牌绑定缓存,过期时间设为5分钟
    var verificationCode = new Random().Next(100000, 999999).ToString();
    你的缓存组件.Set($"login_code_{userId}", new { Code = verificationCode, Token = tokenResult.AccessToken }, TimeSpan.FromMinutes(5));

    // 主动给Teams用户推送验证码
    var connectorClient = new ConnectorClient(new Uri(serviceUrl), new MicrosoftAppCredentials(你的机器人AppID, 你的机器人App密钥));
    var notifyActivity = Activity.CreateMessageActivity();
    notifyActivity.From = new ChannelAccount(你的机器人AppID, "你的机器人名称");
    notifyActivity.Recipient = new ChannelAccount(userId);
    notifyActivity.Conversation = new ConversationAccount(id: conversationId);
    notifyActivity.Text = $"你的登录验证码为:{verificationCode},5分钟内有效";
    await connectorClient.Conversations.SendToConversationAsync((Activity)notifyActivity);

    // 自动关闭登录标签页
    return Content("<script>window.close();</script>", "text/html");
}

// 令牌解析用的实体类
public class TokenResult
{
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }
    [JsonProperty("expires_in")]
    public int ExpiresIn { get; set; }
}

验证码校验逻辑

private async Task HandleVerificationCodeInput(IDialogContext context, IAwaitable<IMessageActivity> result)
{
    var userInput = (await result).Text?.Trim();
    var cacheKey = $"login_code_{context.Activity.From.Id}";
    var cacheData = 你的缓存组件.Get<dynamic>(cacheKey);

    if (cacheData != null && cacheData.Code == userInput)
    {
        // 校验通过,删除缓存,执行业务逻辑
        你的缓存组件.Delete(cacheKey);
        await DoThingsWithToken(context, cacheData.Token);
    }
    else
    {
        await context.PostAsync("验证码错误或已过期,请重新点击登录按钮获取");
        await SendCustomSignInCard(context);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 18:54:03