目录

OpenIddict介绍

OpenIddict 是一个开源的通用框架 ,用于在任何 ASP.NET Core 2.1(及更高版本)和遗留 ASP.NET 4.6.1(及更高版本)应用程序中构建符合标准的 OAuth 2.0/OpenID Connect 服务器。

OpenIddict 诞生于 2015 年底,最初是基于AspNet.Security.OpenIdConnect.Server (代号 ASOS),一个低级的 OpenID Connect 服务器中间件,灵感来自微软为 OWIN 项目开发的 OAuth 2.0 授权服务器中间件和第一个 OpenID曾经为 ASP.NET Core 创建的连接服务器。

2020 年,ASOS 被合并到 OpenIddict 3.0 中,在 OpenIddict 保护伞下形成一个统一的堆栈,同时仍然为新用户提供易于使用的方法,并为高级用户提供低级体验,这得益于允许的“降级模式”以无状态方式使用 OpenIddict(即没有后备数据库)。

Microsoft.Owin作为此过程的一部分, OpenIddict 3.0 中添加了对 的本机支持,以允许在遗留 ASP.NET 4.6.1(及更高版本)应用程序中使用它,使其成为替代OAuthAuthorizationServerMiddlewareOAuthBearerAuthenticationMiddleware无需迁移到 ASP.NET Core 的绝佳候选者。

核心概念

用户认证

与其他解决方案不同,OpenIddict 专门关注授权过程的 OAuth 2.0/OpenID Connect 协议方面, 并将用户身份验证留给实施者:OpenIddict 可以在本地与任何形式的用户身份验证一起使用,例如密码、令牌、联合或集成 Windows 身份验证. 虽然方便,但不需要使用像 ASP.NET Core Identity 这样的成员堆栈。

与 OpenIddict 的集成通常是通过启用直通模式来处理控制器操作或最小 API 处理程序中的请求,或者对于更复杂的场景,直接使用其高级事件模型来完成。

直通模式

与 一样OAuthAuthorizationServerMiddleware,OpenIddict 允许在自定义控制器操作或任何其他能够挂接到 ASP.NET Core 或 OWIN 请求处理管道的中间件中处理授权、注销和令牌请求。在这种情况下,OpenIddict 将始终在允许调用其余管道之前首先验证传入请求(例如,通过确保强制参数存在且有效):如果发生任何验证错误,OpenIddict 将在请求到达用户之前自动拒绝该请求-定义的控制器操作或自定义中间件。

builder.Services.AddOpenIddict()
    .AddServer(options =>
    {
        // Enable the authorization and token endpoints.
        options.SetAuthorizationEndpointUris("/authorize")
               .SetTokenEndpointUris("/token");

        // Enable the authorization code flow.
        options.AllowAuthorizationCodeFlow();

        // Register the signing and encryption credentials.
        options.AddDevelopmentEncryptionCertificate()
               .AddDevelopmentSigningCertificate();

        // Register the ASP.NET Core host and configure the authorization endpoint
        // to allow the /authorize minimal API handler to handle authorization requests
        // after being validated by the built-in OpenIddict server event handlers.
        //
        // Token requests will be handled by OpenIddict itself by reusing the identity
        // created by the /authorize handler and stored in the authorization codes.
        options.UseAspNetCore()
               .EnableAuthorizationEndpointPassthrough();
    });
app.MapGet("/authorize", async (HttpContext context) =>
{
    // Resolve the claims stored in the principal created after the Steam authentication dance.
    // If the principal cannot be found, trigger a new challenge to redirect the user to Steam.
    var principal = (await context.AuthenticateAsync(SteamAuthenticationDefaults.AuthenticationScheme))?.Principal;
    if (principal is null)
    {
        return Results.Challenge(properties: null, new[] { SteamAuthenticationDefaults.AuthenticationScheme });
    }

    var identifier = principal.FindFirst(ClaimTypes.NameIdentifier)!.Value;

    // Create a new identity and import a few select claims from the Steam principal.
    var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
    identity.AddClaim(new Claim(Claims.Subject, identifier));
    identity.AddClaim(new Claim(Claims.Name, identifier).SetDestinations(Destinations.AccessToken));

    return Results.SignIn(new ClaimsPrincipal(identity), properties: null, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
});

事件模型

OpenIddict 为其服务器和验证堆栈实现了一个强大的基于事件的模型:请求处理逻辑的每个部分都被实现为一个事件处理程序,可以将其删除、移动到管道中的不同位置或由自定义处理程序替换以覆盖OpenIddict 使用的默认逻辑:

/// <summary>
/// Contains the logic responsible of rejecting authorization requests that don't specify a valid prompt parameter.
/// </summary>
public class ValidatePromptParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
    /// <summary>
    /// Gets the default descriptor definition assigned to this handler.
    /// </summary>
    public static OpenIddictServerHandlerDescriptor Descriptor { get; }
        = OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
            .UseSingletonHandler<ValidatePromptParameter>()
            .SetOrder(ValidateNonceParameter.Descriptor.Order   1_000)
            .SetType(OpenIddictServerHandlerType.BuiltIn)
            .Build();

    /// <inheritdoc/>
    public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
    {
        if (context is null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        // Reject requests specifying prompt=none with consent/login or select_account.
        if (context.Request.HasPrompt(Prompts.None) && (context.Request.HasPrompt(Prompts.Consent) ||
                                                        context.Request.HasPrompt(Prompts.Login) ||
                                                        context.Request.HasPrompt(Prompts.SelectAccount)))
        {
            context.Logger.LogInformation(SR.GetResourceString(SR.ID6040));

            context.Reject(
                error: Errors.InvalidRequest,
                description: SR.FormatID2052(Parameters.Prompt),
                uri: SR.FormatID8000(SR.ID2052));

            return default;
        }

        return default;
    }
}

在 OpenIddict 本身,事件处理程序通常被定义为专用类,但它们也可以使用委托进行注册:

services.AddOpenIddict()
    .AddServer(options =>
    {
        options.AddEventHandler<HandleConfigurationRequestContext>(builder =>
            builder.UseInlineHandler(context =>
            {
                // Attach custom metadata to the configuration document.
                context.Metadata["custom_metadata"] = 42;

                return default;
            }));
    });