ASP.NET Core中OpenID Connect错误事件处理实战

本文详细介绍了在ASP.NET Core应用程序中处理OpenID Connect错误事件的实现方法,包括配置OpenID Connect认证、使用事件处理器处理远程错误以及创建用户友好的错误页面展示。

在ASP.NET Core中处理OpenID Connect错误事件

ASP.NET Core为处理OpenID Connect错误事件提供了出色的扩展点。本文探讨了在使用ASP.NET Core Identity实现的ASP.NET Core应用程序中实施错误处理的方法。

代码:https://github.com/damienbod/IdentityExternalErrorHandling

设置

应用程序使用OpenID Connect来实现用户身份认证。这实现了标准的OpenID Connect流程,并使用Microsoft Entra ID作为身份提供程序。应用程序还使用了ASP.NET Core Identity,可用于实现用户管理。这不是必需的,我通常在业务应用程序中避免使用它,因为在大多数情况下,这种逻辑可以委托给身份提供程序。

可以使用默认的ASP.NET Core OpenID Connect处理程序来实现任何OpenID Connect实现的逻辑。几乎所有产品和服务都为特定客户端提供客户端实现,并且所有这些都是默认ASP.NET Core接口的包装器。Microsoft为Microsoft Entra产品提供了Microsoft.Identity.Web Nuget包。只要您不在同一应用程序中使用任何其他OAuth或OpenID连接服务,这种方法就能很好地工作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Identity.External
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
}).AddOpenIdConnect("EntraID", "EntraID", oidcOptions =>
{
    oidcOptions.SignInScheme = IdentityConstants.ExternalScheme;
    oidcOptions.SignOutScheme = IdentityConstants.ApplicationScheme;
    oidcOptions.RemoteSignOutPath = new PathString("/signout-callback-oidc-entra");
    oidcOptions.SignedOutCallbackPath = new PathString("/signout-oidc-entra");
    oidcOptions.CallbackPath = new PathString("/signin-oidc-entra");
     
    oidcOptions.Scope.Add("user.read");
    oidcOptions.Authority = $"https://login.microsoftonline.com/{builder.Configuration["AzureAd:TenantId"]}/v2.0/";
    oidcOptions.ClientId = builder.Configuration["AzureAd:ClientId"];
    oidcOptions.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
    oidcOptions.UsePkce = true;
     
    oidcOptions.MapInboundClaims = false;
    oidcOptions.SaveTokens = true;
    oidcOptions.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
    oidcOptions.TokenValidationParameters.RoleClaimType = "role";
});

OpenID Connect事件

在OpenID Connect流程中实现自定义逻辑时,ASP.NET Core实现提供了许多事件。所有这些事件都可以用于特定需求。当实现从身份提供程序返回的错误逻辑时,没有一个事件可以用于此逻辑,因为每个产品或服务对此的实现和支持方式都不同。例如,一些提供程序不返回用户认证错误,而其他提供程序则返回。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
oidcOptions.Events = new OpenIdConnectEvents
{
    // 添加事件处理程序
    OnTicketReceived = async context => {},
    OnRedirectToIdentityProvider = async context => {},
    OnPushAuthorization = async context => {},
    OnMessageReceived = async context => {},
    OnAccessDenied = async context => {},
    OnAuthenticationFailed = async context => {},
    OnRemoteFailure = async context => {},
    // ...
};

处理远程错误

OnRemoteFailure可用于处理流程错误,例如请求中的密钥不正确。HandleResponse可用于防止错误事件的进一步处理,并且用户可以被重定向到用户友好的UI视图。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
OnRemoteFailure = async context =>
{
    var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
    logger.LogInformation("OnRemoteFailure from identity provider. Scheme: {Scheme: }", context.Scheme.Name);
     
    if (context.Failure != null)
    {
        context.HandleResponse();
        context.Response.Redirect($"/Error?remoteError={context.Failure.Message}");
    }
     
    await Task.CompletedTask;
}

UI错误页面

可以使用Razor Page来显示错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }
    public string? Error { get; set; }
    public string? ErrorDescription { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
     
    public void OnGet(string? remoteError)
    {
        if (remoteError != null)
        {
            Error = "Remote authentication error";
            ErrorDescription = remoteError;
        }
         
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
    }
}

注意事项

用于实现OpenID Connect的ASP.NET Core API非常出色。所有实现OpenID Connect服务器的产品和服务都以不同的方式处理错误处理。根据软件中使用的身份产品,需要不同的事件来处理这种情况。

链接

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计