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

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端在项目签名配置中设置。

验证流程

  1. 从Swift应用登出,确认钥匙链中无MSAL账户数据
  2. 打开Xamarin应用完成登录(此时使用正常流程,无需强制输入)
  3. 打开Swift应用,调用loadCurrentAccount应能自动获取Xamarin端的登录账户,完成SSO

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 06:06:30