MSAL-SSO跨Xamarin与Swift应用登出后重登自动登录失效问题
问题描述
我们有基于Xamarin和Swift开发的移动应用,使用Azure AD和MSAL实现了单点登录(SSO)功能。正常场景下SSO工作正常:
- 从Xamarin应用登录后,Swift应用可自动完成登录
- 从Swift应用登录后,Xamarin应用也可自动登录
但存在异常情况:从Swift应用登出后,返回Xamarin应用完成登录,此时SSO功能失效,Swift应用无法自动登录。
Xamarin 登录代码
public async Task<bool> SignInAsync() { try { var accounts = await _pca.GetAccountsAsync(); var firstAccount = accounts.FirstOrDefault(); var authResult = await _pca .AcquireTokenSilent(Scopes, firstAccount) .ExecuteAsync(); return true; } catch (MsalUiRequiredException) { try { // 这意味着我们需要通过MSAL窗口重新登录。 var authResult = await _pca.AcquireTokenInteractive(Scopes).WithPrompt(Prompt.ForceLogin) .WithUseEmbeddedWebView(false) //.WithParentActivityOrWindow(App.UIParent) .ExecuteAsync(); return true; } catch (Exception ex2) { Debug.WriteLine(ex2.ToString()); return false; } } catch (Exception ex) { Debug.WriteLine(ex.ToString()); return false; } }
Xamarin 登出代码
public async Task SignOutAsync(bool isFromAutoLogout = false) { try { var accounts = await _pca.GetAccountsAsync(); // 遍历所有账户并移除。 while (accounts.Any()) { await _pca.RemoveAsync(accounts.FirstOrDefault()); accounts = await _pca.GetAccountsAsync(); } } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } }
Swift 登录代码
func callLoadAcct() { initWebViewParams() self.loadCurrentAccount { (account) in guard let currentAccount = account else { // 检查是否存在当前登录账户 // 如果不存在,则需要用户登录 self.acquireTokenInteractively() return } self.acquireTokenSilently(currentAccount) } } typealias AccountCompletion = (MSALAccount?) -> Void func loadCurrentAccount(completion: AccountCompletion? = nil) { guard let applicationContext = MSALManager.shared.applicationContext else { return } let msalParameters = MSALParameters() msalParameters.completionBlockQueue = DispatchQueue.main applicationContext.getCurrentAccount(with: msalParameters, completionBlock: { (currentAccount, previousAccount, error) in if let error = error { return } if let currentAccount = currentAccount { self.updateCurrentAccount(account: currentAccount) if let completion = completion { completion(MSALManager.shared.currentAccount) } return } MSALManager.shared.accessToken = "" MSALManager.shared.originalAuthTokenExpire = nil self.updateCurrentAccount(account: nil) if let completion = completion { completion(nil) } }) }
Swift 登出代码
func signOut() { self.appdelegate = UIApplication.shared.delegate as? AppDelegate guard let applicationContext = MSALManager.shared.applicationContext else { return } guard let account = MSALManager.shared.currentAccount else { return } do { MSALManager.shared.webViewParameters = MSALWebviewParameters(authPresentationViewController: self.appdelegate.window?.rootViewController ?? UIViewController()) let signoutParameters = MSALSignoutParameters(webviewParameters: MSALManager.shared.webViewParameters!) signoutParameters.signoutFromBrowser = false // 如果需要同时从浏览器或WebView登出,请将此设置为true applicationContext.signout(with: account, signoutParameters: signoutParameters, completionBlock: {(success, error) in if let error = error { return } MSALManager.shared.accessToken = "" MSALManager.shared.originalAuthTokenExpire = nil self.timer?.invalidate() MSALManager.shared.currentAccount = nil self.performLogoutAction() }) } }
问题原因及修复方案
核心问题
Swift端登出时未彻底清理跨应用共享的SSO缓存(iOS钥匙链中的MSAL账户数据),且Xamarin端登录时使用Prompt.ForceLogin强制用户重新输入凭证,导致新生成的登录状态无法同步到跨应用SSO缓存中。
具体修复步骤
1. 修改Swift登出代码,彻底清理SSO缓存
将signoutFromBrowser设为true,并遍历所有账户完成登出,确保钥匙链中的MSAL缓存完全清除:
func signOut() { self.appdelegate = UIApplication.shared.delegate as? AppDelegate guard let applicationContext = MSALManager.shared.applicationContext else { return } // 获取所有账户并逐个登出 applicationContext.allAccounts { (accounts, error) in guard let accounts = accounts else { self.cleanupLocalState() return } for account in accounts { do { let webViewParams = MSALWebviewParameters(authPresentationViewController: self.appdelegate.window?.rootViewController ?? UIViewController()) let signoutParams = MSALSignoutParameters(webviewParameters: webViewParams) signoutParams.signoutFromBrowser = true // 强制清理浏览器/SSO共享缓存 signoutParams.signoutFromAllDevices = false // 按需设置是否跨设备登出 applicationContext.signout(with: account, signoutParameters: signoutParams) { _, _ in } } catch {} } self.cleanupLocalState() } } // 提取本地状态清理逻辑 private func cleanupLocalState() { MSALManager.shared.accessToken = "" MSALManager.shared.originalAuthTokenExpire = nil self.timer?.invalidate() MSALManager.shared.currentAccount = nil self.performLogoutAction() }
2. 修改Xamarin登录代码,避免强制登录打断SSO同步
Prompt.ForceLogin会绕过SSO缓存强制用户重新验证,导致新状态无法同步到跨应用缓存。改用Prompt.SelectAccount允许用户选择已有账户,恢复正常SSO流程:
public async Task<bool> SignInAsync() { try { var accounts = await _pca.GetAccountsAsync(); var firstAccount = accounts.FirstOrDefault(); var authResult = await _pca .AcquireTokenSilent(Scopes, firstAccount) .ExecuteAsync(); return true; } catch (MsalUiRequiredException) { try { // 改用SelectAccount,保留SSO同步能力 var authResult = await _pca.AcquireTokenInteractive(Scopes) .WithPrompt(Prompt.SelectAccount) .WithUseEmbeddedWebView(false) //.WithParentActivityOrWindow(App.UIParent) .ExecuteAsync(); return true; } catch (Exception ex2) { Debug.WriteLine(ex2.ToString()); return false; } } catch (Exception ex) { Debug.WriteLine(ex.ToString()); return false; } }
3. 确认跨应用钥匙链配置一致
iOS端需确保Xamarin和Swift应用配置了相同的钥匙链访问组,MSAL才能跨应用共享缓存。Xamarin端可在Info.plist中添加,Swift端在项目签名配置中设置。
验证流程
- 从Swift应用登出,确认钥匙链中无MSAL账户数据
- 打开Xamarin应用完成登录(此时使用正常流程,无需强制输入)
- 打开Swift应用,调用
loadCurrentAccount应能自动获取Xamarin端的登录账户,完成SSO
内容的提问来源于stack exchange,提问作者ramdev
相关产品推荐
相关产品推荐

