处理 MSAL for iOS/macOS 中的错误和异常

本文概述不同类型的错误,并提供处理常见登录错误的相关建议。

MSAL 错误处理基础知识

Microsoft 身份验证库 (MSAL) 中的异常旨在帮助应用开发者进行故障排除,而不会向最终用户显示。 异常消息未经本地化。

处理异常和错误时,可以使用异常类型本身和错误代码来区分不同的异常。 有关错误代码的列表,请参阅 Microsoft Entra 身份验证和授权错误代码

在登录体验期间,可能会遇到有关许可、条件访问(MFA、设备管理、基于位置的限制)、令牌颁发和兑换以及用户属性的错误。

以下部分提供了有关应用错误处理的更多详细信息。

MSAL for iOS/macOS 中的错误处理

MSALError 枚举中列出了适用于 iOS 和 macOS 的 MSAL 完整错误列表。

生成的所有 MSAL 错误都以 MSALErrorDomain 域返回。

对于系统错误,MSAL 会从系统 API 返回原始 NSError。 例如,如果由于缺少网络连接,令牌获取失败,则 MSAL 会返回包含 NSURLErrorDomain 域和 NSURLErrorNotConnectedToInternet 代码的错误。

建议在客户端上至少处理以下两个 MSAL 错误:

  • MSALErrorInteractionRequired:用户必须执行交互式请求。 很多条件都可能会导致此错误,例如身份验证会话过期或需要额外的身份验证要求。 调用 MSAL 交互式令牌获取 API 以进行恢复。

  • MSALErrorServerDeclinedScopes:部分或全部作用域已被拒绝。 决定是继续使用唯一允许的作用域,还是停止登录过程。

注意

MSALInternalError 枚举应仅用于引用和调试。 请勿尝试在运行时自动处理这些错误。 如果应用遇到属于 MSALInternalError 的任何错误,则可能希望显示一条面向用户的通用消息,说明所发生的情况。

例如,MSALInternalErrorBrokerResponseNotReceived 表示用户未完成身份验证并已手动返回到应用。 在这种情况下,应用应显示一条通用错误消息,说明身份验证未完成,并建议他们尝试再次进行身份验证。

以下 Objective-C 示例代码演示了处理某些常见错误状况的最佳做法。

    MSALInteractiveTokenParameters *interactiveParameters = ...;
    MSALSilentTokenParameters *silentParameters = ...;
    
    MSALCompletionBlock completionBlock;
    __block __weak MSALCompletionBlock weakCompletionBlock;
    
    weakCompletionBlock = completionBlock = ^(MSALResult *result, NSError *error)
    {
        if (!error)
        {
            // Use result.accessToken
            NSString *accessToken = result.accessToken;
            return;
        }
        
        if ([error.domain isEqualToString:MSALErrorDomain])
        {
            switch (error.code)
            {
                case MSALErrorInteractionRequired:
                {
                    // Interactive auth will be required
                    [application acquireTokenWithParameters:interactiveParameters
                                            completionBlock:weakCompletionBlock];
                    
                    break;
                }
                    
                case MSALErrorServerDeclinedScopes:
                {
                    // These are list of granted and declined scopes.
                    NSArray *grantedScopes = error.userInfo[MSALGrantedScopesKey];
                    NSArray *declinedScopes = error.userInfo[MSALDeclinedScopesKey];
                    
                    // To continue acquiring token for granted scopes only, do the following
                    silentParameters.scopes = grantedScopes;
                    [application acquireTokenSilentWithParameters:silentParameters
                                                  completionBlock:weakCompletionBlock];
                    
                    // Otherwise, instead, handle error fittingly to the application context
                    break;
                }
                    
                case MSALErrorServerProtectionPoliciesRequired:
                {
                    // Integrate the Intune SDK and call the
                    // remediateComplianceForIdentity:silent: API.
                    // Handle this error only if you integrated Intune SDK.
                                        
                    break;
                }
                    
                case MSALErrorUserCanceled:
                {
                    // The user cancelled the web auth session.
                    // You may want to ask the user to try again.
                    // Handling of this error is optional.
                    
                    break;
                }
                    
                case MSALErrorInternal:
                {
                    // Log the error, then inspect the MSALInternalErrorCodeKey
                    // in the userInfo dictionary.
                    // Display generic error message to the end user
                    // More detailed information about the specific error
                    // under MSALInternalErrorCodeKey can be found in MSALInternalError enum.
                    NSLog(@"Failed with error %@", error);
                    
                    break;
                }
                    
                default:
                    NSLog(@"Failed with unknown MSAL error %@", error);
                    
                    break;
            }
            
            return;
        }
        
        // Handle no internet connection.
        if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorNotConnectedToInternet)
        {
            NSLog(@"No internet connection.");
            return;
        }
        
        // Other errors may require trying again later,
        // or reporting authentication problems to the user.
        NSLog(@"Failed with error %@", error);
    };
    
    // Acquire token silently
    [application acquireTokenSilentWithParameters:silentParameters
                                  completionBlock:completionBlock];

     // or acquire it interactively.
     [application acquireTokenWithParameters:interactiveParameters
                             completionBlock:completionBlock];
    let interactiveParameters: MSALInteractiveTokenParameters = ...
    let silentParameters: MSALSilentTokenParameters = ...
            
    var completionBlock: MSALCompletionBlock!
    completionBlock = { (result: MSALResult?, error: Error?) in
                
        if let result = result
        {
            // Use result.accessToken
            let accessToken = result.accessToken
            return
        }

        guard let error = error as NSError? else { return }

        if error.domain == MSALErrorDomain, let errorCode = MSALError(rawValue: error.code)
        {
            switch errorCode
            {
                case .interactionRequired:
                    // Interactive auth will be required
                    application.acquireToken(with: interactiveParameters, completionBlock: completionBlock)

                case .serverDeclinedScopes:
                    let grantedScopes = error.userInfo[MSALGrantedScopesKey]
                    let declinedScopes = error.userInfo[MSALDeclinedScopesKey]

                    if let scopes = grantedScopes as? [String] {
                        silentParameters.scopes = scopes
                        application.acquireTokenSilent(with: silentParameters, completionBlock: completionBlock)
                    }
                        
                    case .serverProtectionPoliciesRequired:
                        // Integrate the Intune SDK and call the
                        // remediateComplianceForIdentity:silent: API.
                        // Handle this error only if you integrated Intune SDK.
                        
                        break
                        
                    case .userCanceled:
                       // The user cancelled the web auth session.
                       // You may want to ask the user to try again.
                       // Handling of this error is optional.
                       break
                        
                    case .internal:
                        // Log the error, then inspect the MSALInternalErrorCodeKey
                        // in the userInfo dictionary.
                        // Display generic error message to the end user
                        // More detailed information about the specific error
                        // under MSALInternalErrorCodeKey can be found in MSALInternalError enum.
                        print("Failed with error \(error)");
                        
                    default:
                        print("Failed with unknown MSAL error \(error)")
            }
        }
                
        // Handle no internet connection.
        if error.domain == NSURLErrorDomain && error.code == NSURLErrorNotConnectedToInternet
        {
            print("No internet connection.")
            return
        }
                
        // Other errors may require trying again later,
        // or reporting authentication problems to the user.
        print("Failed with error \(error)");    
    }
   
    // Acquire token silently
    application.acquireToken(with: interactiveParameters, completionBlock: completionBlock)
 
    // or acquire it interactively.
    application.acquireTokenSilent(with: silentParameters, completionBlock: completionBlock)

条件访问和声明质询

以静默方式获取令牌时,如果你尝试访问的 API 需要条件访问声明质询(例如 MFA 策略),则应用程序可能会收到错误。

处理此错误的模式是使用 MSAL 以交互方式获取令牌。 这会提示用户,并使他们能够满足所需的条件访问策略。

在某些情况下调用需要条件访问的 API 时,API 返回的错误中可能会包含声明质询。 例如,如果条件访问策略要求使用托管设备 (Intune),则错误将类似于 AADSTS53000:需要管理你的设备才能访问此资源。 在这种情况下,可以在令牌获取调用中传递声明,使系统提示用户,以满足相应的策略。

借助适用于 iOS 和 macOS 的 MSAL,你可以在交互式和静默令牌获取方案中请求特定的声明。

要请求自定义声明,请在 MSALSilentTokenParametersMSALInteractiveTokenParameters 中指定 claimsRequest

有关详细信息,请参阅使用适用于 iOS 和 macOS 的 MSAL 请求自定义声明

出现错误和异常后重试

调用 MSAL 时,应实现自己的重试策略。 MSAL 会对 Microsoft Entra 服务进行 HTTP 调用,有时会失败。 例如网络崩溃或服务器重载。

HTTP 429

如果服务令牌服务器 (STS) 因请求过多而重载,则将返回 HTTP 错误 429,并在 Retry-After 响应字段中提示还要多久才能重试。

后续步骤

请考虑启用 MSAL for iOS/macOS 中的日志记录,以帮助你诊断和调试问题。