使用TeamsSSOTokenExchangeMiddleware时OnTeamsSigninVerifyStateAsync不触发,且Teams中SSO首次登录需二次重试才能成功
我能理解你遇到的这个Teams SSO问题有多闹心——登出后首次触发登录得重试一次才成功,而且OnTeamsSigninVerifyStateAsync在启用TeamsSSOTokenExchangeMiddleware时还完全不触发,明明微软官方示例里是同时用这俩的,自己用就出问题,确实很懵。
结合你描述的现象(首次GetToken返回404、第二次200,去掉中间件后状态验证方法触发且首次登录成功),我来拆解下问题原因和对应的解决思路:
一、核心问题分析
中间件与状态验证方法的职责冲突
TeamsSSOTokenExchangeMiddleware的作用是自动拦截Teams发起的SSO令牌交换请求,帮你跳过手动OAuth卡片的步骤。但如果中间件处理请求时,没有正确传递状态或者和你的OnTeamsSigninVerifyStateAsync逻辑重叠,就会导致状态验证方法不被触发——毕竟中间件已经“抢先”处理了请求,后续的状态验证流程就走不通了。登出后的缓存/状态不同步
你提到登出后首次请求GetToken返回404,这大概率是登出操作没有清理全所有关联状态:
- Bot Framework端的用户令牌缓存可能残留了无效的令牌记录
- Teams客户端或对话状态里的旧登录状态没有被重置,导致第一次请求时找不到有效令牌,第二次请求时状态才同步完成
二、针对性解决方案
1. 调整OnTeamsSigninVerifyStateAsync的实现逻辑
微软示例里同时用中间件和这个方法,核心是要让两者的职责明确:中间件处理自动令牌交换,状态验证方法处理手动登录后的状态确认。你当前的实现是直接在方法里重新运行登录对话框,这会和中间件的自动处理冲突。修改成这样:
protected override async Task OnTeamsSigninVerifyStateAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken) { // 先调用基类方法完成状态验证,确保Teams的登录状态被正确处理 await base.OnTeamsSigninVerifyStateAsync(turnContext, cancellationToken); // 再运行登录对话框处理后续逻辑 await _loginDialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken); }
加上基类方法的调用后,中间件和状态验证流程就能正确协作了,不会出现一方“抢了另一方的活”导致方法不触发的情况。
2. 确保登出操作清理全所有关联状态
你当前的SignOutUserAsync调用可能没有传全必要参数,导致缓存清理不彻底。修改登出逻辑:
// 确保传入正确的连接名、用户ID和租户ID await _userTokenClient.SignOutUserAsync( turnContext.Activity.From.Id, "你的OAuth连接名称", // 要和OAuthPrompt用的连接名完全一致 turnContext.Activity.Conversation.TenantId, cancellationToken ); // 同时重置对话状态里的登录相关状态,避免残留旧数据 var dialogState = await _conversationState.CreateProperty<DialogState>(nameof(DialogState)).GetAsync(turnContext, () => new DialogState(), cancellationToken); dialogState.Stack.Clear(); await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
这样登出后,Bot端的令牌缓存和对话状态都会被彻底清理,不会影响下一次登录请求。
3. 检查OAuthPrompt和中间件的配置一致性
- 确保
TeamsSSOTokenExchangeMiddleware初始化时用的连接名,和OAuthPrompt、Bot通道设置里的连接名称完全一致:// 中间件初始化示例 var teamsSSOMiddleware = new TeamsSSOTokenExchangeMiddleware(_configuration["ConnectionName"], _userTokenClient); adapter.Use(teamsSSOMiddleware); - 给OAuthPrompt启用令牌交换支持,确保Teams能自动触发SSO:
var oauthPrompt = new OAuthPrompt(nameof(OAuthPrompt), new OAuthPromptSettings { ConnectionName = "你的OAuth连接名称", Text = "请完成登录", Title = "SSO登录", TokenExchangeSettings = new TokenExchangeSettings { IsTokenExchangeSupported = true } });
4. 排查GetToken 404的深层原因
如果上述调整后还是有404问题,可以:
- 用Bot Framework Emulator测试,看是否在模拟器里也出现同样的问题——如果模拟器正常,说明是Teams客户端的状态同步问题,登出后重启Teams客户端再试
- 检查Bot通道的OAuth连接设置,确保令牌交换URL配置正确(应该是
https://token.botframework.com/api/oauth/tokenexchange)
三、验证效果
调整完成后,按这个流程测试:
- 触发登出操作,确认对话状态和令牌缓存被清理
- 发起第一次登录请求,看
GetToken是否返回200,OnTeamsSigninVerifyStateAsync是否触发 - 确认登录流程一次成功,无需重试
内容来源于stack exchange

