项目

ABP OpenIddict 模块

OpenIddict 模块提供了与 OpenIddict 的集成,OpenIddict 提供了高级认证功能,如单点登录、单一注销和 API 访问控制。此模块将应用程序、范围和其他 OpenIddict 相关对象持久化到数据库中。

如何安装

此模块作为预安装(作为 NuGet/NPM 包)提供。您可以继续将其作为包使用并轻松获取更新,或者您可以将源代码包含到您的解决方案中(参见 get-source CLI 命令)以开发您的自定义模块。

源代码

此模块的源代码可以在此处访问。源代码采用 MIT 许可,因此您可以自由使用和定制它。

用户界面

此模块实现了领域逻辑和数据库集成,但不提供任何 UI。如果您需要动态添加应用程序和范围,管理 UI 会很有用。在这种情况下,您可以自己构建管理 UI,或者考虑购买 ABP,它为此模块提供了管理 UI。

与其他模块的关系

此模块基于 Identity 模块,并与 Account 模块有一个集成包

选项

OpenIddictBuilder

OpenIddictBuilder 可以在您的 OpenIddict 模块PreConfigureServices 方法中配置。

示例:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<OpenIddictBuilder>(builder =>
    {
        //在此设置选项...
    });
}

OpenIddictBuilder 包含多种扩展方法来配置 OpenIddict 服务:

  • AddServer() 在 DI 容器中注册 OpenIddict 令牌服务器服务。包含 OpenIddictServerBuilder 配置。
  • AddCore() 在 DI 容器中注册 OpenIddict 核心服务。包含 OpenIddictCoreBuilder 配置。
  • AddValidation() 在 DI 容器中注册 OpenIddict 令牌验证服务。包含 OpenIddictValidationBuilder 配置。

OpenIddictCoreBuilder

OpenIddictCoreBuilder 包含用于配置 OpenIddict 核心服务的扩展方法。

示例:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<OpenIddictCoreBuilder>(builder =>
    {
        //在此设置选项...
    });
}

这些服务包括:

  • 添加 ApplicationStoreAuthorizationStoreScopeStoreTokenStore
  • 替换 ApplicationManagerAuthorizationManagerScopeManagerTokenManager
  • 替换 ApplicationStoreResolverAuthorizationStoreResolverScopeStoreResolverTokenStoreResolver
  • 设置 DefaultApplicationEntityDefaultAuthorizationEntityDefaultScopeEntityDefaultTokenEntity

OpenIddictServerBuilder

OpenIddictServerBuilder 包含用于配置 OpenIddict 服务器服务的扩展方法。

示例:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<OpenIddictServerBuilder>(builder =>
    {
        //在此设置选项...
    });
}

这些服务包括:

  • 注册声明、范围。
  • 设置 Issuer URI,该 URI 用作从发现端点返回的端点 URI 的基础地址。
  • 添加开发签名密钥、加密/签名密钥、凭据和证书。
  • 添加/删除事件处理器。
  • 启用/禁用授权类型。
  • 设置认证服务器端点 URI。

OpenIddictValidationBuilder

OpenIddictValidationBuilder 包含用于配置 OpenIddict 验证服务的扩展方法。

示例:

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<OpenIddictValidationBuilder>(builder =>
    {
        //在此设置选项...
    });
}

这些服务包括:

  • AddAudiences() 用于资源服务器。
  • SetIssuer() URI,用于在使用提供者发现时确定 OAuth 2.0/OpenID Connect 配置文档的实际位置。
  • SetConfiguration() 用于配置 OpenIdConnectConfiguration
  • UseIntrospection() 使用内省而不是本地/直接验证。
  • 添加加密密钥、凭据和证书。
  • 添加/删除事件处理器。
  • SetClientId() 用于在与远程授权服务器通信时设置客户端标识符 client_id(例如用于内省)。
  • SetClientSecret() 用于在与远程授权服务器通信时设置标识符 client_secret(例如用于内省)。
  • EnableAuthorizationEntryValidation() 启用授权验证,通过为每个 API 请求进行数据库调用来确保 access token 仍然有效。*注意:*这可能会对性能产生负面影响,并且只能与基于 OpenIddict 的授权服务器一起使用。
  • EnableTokenEntryValidation() 启用授权验证,通过为每个 API 请求进行数据库调用来确保 access token 仍然有效。*注意:*这可能会对性能产生负面影响,并且在 OpenIddict 服务器配置为使用引用令牌时是必需的。
  • UseLocalServer() 注册 OpenIddict 验证/服务器集成服务。
  • UseAspNetCore() 在 DI 容器中为 ASP.NET Core 注册 OpenIddict 验证服务。

内部结构

领域层

聚合

OpenIddictApplication

OpenIddictApplications 表示可以从您的 OpenIddict 服务器请求令牌的应用程序。

  • OpenIddictApplications(聚合根):表示一个 OpenIddict 应用程序。
    • ClientId (字符串):与当前应用程序关联的客户端标识符。
    • ClientSecret (字符串):与当前应用程序关联的客户端密钥。出于安全原因,可能被哈希或加密。
    • ConsentType (字符串):与当前应用程序关联的同意类型。
    • DisplayName (字符串):与当前应用程序关联的显示名称。
    • DisplayNames (字符串):与当前应用程序关联的本地化显示名称,序列化为 JSON 对象。
    • Permissions (字符串):与当前应用程序关联的权限,序列化为 JSON 数组。
    • PostLogoutRedirectUris (字符串):与当前应用程序关联的注销回调 URL,序列化为 JSON 数组。
    • Properties (字符串):与当前应用程序关联的附加属性,序列化为 JSON 对象或 null。
    • RedirectUris (字符串):与当前应用程序关联的回调 URL,序列化为 JSON 数组。
    • Requirements (字符串):与当前应用程序关联的要求。
    • Type (字符串):与当前应用程序关联的应用程序类型。
    • ClientUri (字符串):关于客户端的更多信息的 URI。
    • LogoUri (字符串):客户端徽标的 URI。
OpenIddictAuthorization

OpenIddictAuthorizations 用于保存允许的范围、授权流程类型。

  • OpenIddictAuthorization(聚合根):表示一个 OpenIddict 授权。
    • ApplicationId (Guid?):与当前授权关联的应用程序。
    • Properties (字符串):与当前授权关联的附加属性,序列化为 JSON 对象或 null。
    • Scopes (字符串):与当前授权关联的范围,序列化为 JSON 数组。
    • Status (字符串):当前授权的状态。
    • Subject (字符串):与当前授权关联的主题。
    • Type (字符串):当前授权的类型。
OpenIddictScope

OpenIddictScopes 用于保存资源的范围。

  • OpenIddictScope(聚合根):表示一个 OpenIddict 范围。
    • Description (字符串):与当前范围关联的公共描述。
    • Descriptions (字符串):与当前范围关联的本地化公共描述,序列化为 JSON 对象。
    • DisplayName (字符串):与当前范围关联的显示名称。
    • DisplayNames (字符串):与当前范围关联的本地化显示名称,序列化为 JSON 对象。
    • Name (字符串):与当前范围关联的唯一名称。
    • Properties (字符串):与当前范围关联的附加属性,序列化为 JSON 对象或 null。
    • Resources (字符串):与当前范围关联的资源,序列化为 JSON 数组。
OpenIddictToken

OpenIddictTokens 用于持久化应用程序令牌。

  • OpenIddictToken(聚合根):表示一个 OpenIddict 令牌。
    • ApplicationId (Guid?):与当前令牌关联的应用程序。
    • AuthorizationId (Guid?):与当前令牌关联的授权。
    • CreationDate (DateTime?):当前令牌的 UTC 创建日期。
    • ExpirationDate (DateTime?):当前令牌的 UTC 过期日期。
    • Payload (字符串):当前令牌的有效负载(如果适用)。仅用于引用令牌,出于安全原因可能被加密。
    • Properties (字符串):与当前令牌关联的附加属性,序列化为 JSON 对象或 null。
    • RedemptionDate (DateTime?):当前令牌的 UTC 兑换日期。
    • ReferenceId (字符串):与当前令牌关联的引用标识符(如果适用)。仅用于引用令牌,出于安全原因可能被哈希或加密。
    • Status (字符串):当前令牌的状态。
    • Subject (字符串):与当前令牌关联的主题。
    • Type (字符串):当前令牌的类型。

存储

此模块实现了 OpenIddict 存储:

  • IAbpOpenIdApplicationStore
  • IOpenIddictAuthorizationStore
  • IOpenIddictScopeStore
  • IOpenIddictTokenStore

AbpOpenIddictStoreOptions

您可以配置 AbpOpenIddictStoreOptionsPruneIsolationLevel/DeleteIsolationLevel 来设置存储操作的事务隔离级别,因为不同的数据库有不同的隔离级别。

存储库

此模块中定义了以下自定义存储库:

  • IOpenIddictApplicationRepository
  • IOpenIddictAuthorizationRepository
  • IOpenIddictScopeRepository
  • IOpenIddictTokenRepository
领域服务

此模块不包含任何领域服务,但重写了以下服务:

  • AbpApplicationManager 用于填充/获取包含 ClientUriLogoUriAbpApplicationDescriptor 信息。

数据库提供程序

通用

表/集合前缀与架构

默认情况下,所有表/集合都使用 OpenIddict 前缀。如果您需要更改表前缀或设置架构名称(如果您的数据库提供程序支持),请在 AbpOpenIddictDbProperties 类上设置静态属性。

连接字符串

此模块使用 AbpOpenIddict 作为连接字符串名称。如果您未定义具有此名称的连接字符串,它将回退到 Default 连接字符串。

有关详细信息,请参阅连接字符串文档。

Entity Framework Core

  • OpenIddictApplications
  • OpenIddictAuthorizations
  • OpenIddictScopes
  • OpenIddictTokens

MongoDB

集合
  • OpenIddictApplications
  • OpenIddictAuthorizations
  • OpenIddictScopes
  • OpenIddictTokens

ASP.NET Core 模块

此模块集成了 ASP.NET Core,为四种协议提供了内置的 MVC 控制器。它使用 OpenIddict 的 直通模式

AuthorizeController -> connect/authorize
TokenController     -> connect/token
LogoutController    -> connect/logout
UserInfoController  -> connect/userinfo

设备流 的实现将在商业模块中完成。

AbpOpenIddictAspNetCoreOptions

AbpOpenIddictAspNetCoreOptions 可以在您的 OpenIddict 模块PreConfigureServices 方法中配置。

示例:

PreConfigure<AbpOpenIddictAspNetCoreOptions>(options =>
{
    //在此设置选项...
});

AbpOpenIddictAspNetCoreOptions 属性:

  • UpdateAbpClaimTypes(default: true):更新 AbpClaimTypes 以兼容 Openiddict 声明。
  • AddDevelopmentEncryptionAndSigningCertificate(default: true):注册(并在必要时生成)用户特定的开发加密/开发签名证书。这是用于签名和加密令牌的证书,仅用于开发环境。对于非开发环境,您必须将其设置为 false

AddDevelopmentEncryptionAndSigningCertificate 不能用于部署在 IIS 或 Azure App Service 上的应用程序:尝试在 IIS 或 Azure App Service 上使用它们将在运行时导致异常(除非应用程序池配置为加载用户配置文件)。为避免这种情况,请考虑创建自签名证书并将其存储在主机机器的 X.509 证书存储中。请参阅:https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-development-certificate

自动移除孤立的令牌/授权

自动移除孤立令牌/授权的后台任务。可以通过 TokenCleanupOptions 来配置管理它。

TokenCleanupOptions 可以在您的 OpenIddict 模块ConfigureServices 方法中配置。

示例:

Configure<TokenCleanupOptions>(options =>
{
    //在此设置选项...
});

TokenCleanupOptions 属性:

  • IsCleanupEnabled (默认:true):启用/禁用令牌清理。
  • CleanupPeriod (默认:3,600,000 毫秒):设置清理周期。
  • DisableAuthorizationPruning:设置一个布尔值,指示是否应禁用授权修剪。
  • DisableTokenPruning:设置一个布尔值,指示是否应禁用令牌修剪。
  • MinimumAuthorizationLifespan (默认:14 天):设置授权必须具有的最短生命周期才能被修剪。不能少于 10 分钟。
  • MinimumTokenLifespan (默认:14 天):设置令牌必须具有的最短生命周期才能被修剪。不能少于 10 分钟。

在 Access_token 和 Id_token 中更新声明

声明主体工厂 可用于向 ClaimsPrincipal 添加/删除声明。

AbpDefaultOpenIddictClaimsPrincipalHandler 服务将默认将 NameEmailRole 类型的声明添加到 access_tokenid_token 中,其他声明默认仅添加到 access_token,并移除 IdentitySecurityStampClaimType 秘密声明。

创建一个继承自 IAbpOpenIddictClaimsPrincipalHandler 的服务并将其添加到 DI 中,以完全控制声明的目标。

public class MyClaimDestinationsHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency
{
    public virtual Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context)
    {
        foreach (var claim in context.Principal.Claims)
        {
            if (claim.Type == MyClaims.MyClaimsType)
            {
                claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
            }
     
            if (claim.Type == MyClaims.MyClaimsType2)
            {
                claim.SetDestinations(OpenIddictConstants.Destinations.AccessToken);
            }
        }

        return Task.CompletedTask;
    }
}

Configure<AbpOpenIddictClaimsPrincipalOptions>(options =>
{
    options.ClaimsPrincipalHandlers.Add<MyClaimDestinationsHandler>();
});

详细资料请参阅:OpenIddict 声明目标

禁用访问令牌加密

ABP 默认为兼容性禁用了 access token encryption,如果需要可以手动启用。

public override void PreConfigureServices(ServiceConfigurationContext context)
{
    PreConfigure<OpenIddictServerBuilder>(builder =>
    {
        builder.Configure(options => options.DisableAccessTokenEncryption = false);
    });
}

禁用传输安全要求

默认情况下,OpenIddict 要求所有端点使用 HTTPS。如果需要,您可以禁用它。您只需配置 OpenIddictServerAspNetCoreOptions 并将 DisableTransportSecurityRequirement 设置为 true

Configure<OpenIddictServerAspNetCoreOptions>(options =>
{
    options.DisableTransportSecurityRequirement = true;
});

https://documentation.openiddict.com/configuration/token-formats.html#disabling-jwt-access-token-encryption

请求/响应过程

OpenIddict.Server.AspNetCore 添加了一个认证方案(Name: OpenIddict.Server.AspNetCore, handler: OpenIddictServerAspNetCoreHandler)并实现了 IAuthenticationRequestHandler 接口。

它将在 AuthenticationMiddleware 中首先执行,并可以短路当前请求。否则,将调用 DefaultAuthenticateScheme 并继续执行管道。

OpenIddictServerAspNetCoreHandler 将调用各种内置处理器(处理请求和响应),处理器将根据上下文处理或跳过与其无关的逻辑。

令牌请求示例:

POST /connect/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

    grant_type=password&
    client_id=AbpApp&
    client_secret=1q2w3e*&
    username=admin&
    password=1q2w3E*&
    scope=AbpAPI offline_access

此请求将由各种处理器处理。它们将确认请求的端点类型,检查 HTTP/HTTPS,验证请求参数(clientscope 等)是否有效并存在于数据库中,等等。进行各种协议检查。并构建一个 OpenIddictRequest 对象。如果有任何错误,可能会设置响应内容并直接短路当前请求。

如果一切正常,请求将转到我们的处理控制器(例如 TokenController),此时我们可以从 HTTP 请求中获取 OpenIddictRequest 对象。其余处理将基于此对象进行。

检查请求中的 usernamepassword。如果正确,则创建一个 ClaimsPrincipal 对象并返回一个 SignInResult,它使用 OpenIddict.Validation.AspNetCore 认证方案名称,将调用 OpenIddictServerAspNetCoreHandler 进行处理。

OpenIddictServerAspNetCoreHandler 进行一些检查以生成 JSON 并替换 HTTP 响应内容。

ForbidResultChallengeResult 的处理方式同上。

如果您需要自定义 OpenIddict,则需要替换/删除/添加新的处理器,并使其以正确的顺序执行。

请参考: https://documentation.openiddict.com/guides/index.html#events-model

PKCE

https://documentation.openiddict.com/configuration/proof-key-for-code-exchange.html

设置令牌生命周期

更新 AuthServerModule(或者如果没有分层/独立的认证服务器,则更新 HttpApiHostModule)文件的 PreConfigureServices 方法:

PreConfigure<OpenIddictServerBuilder>(builder =>
{
    builder.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(30));
    builder.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
    builder.SetIdentityTokenLifetime(TimeSpan.FromMinutes(30));
    builder.SetRefreshTokenLifetime(TimeSpan.FromDays(14));
});

刷新令牌

要使用刷新令牌,它必须得到 OpenIddictServer 的支持,并且应用程序必须请求 refresh_token

注意: Angular 应用程序已配置为使用 refresh_token

配置 OpenIddictServer

更新 OpenIddictDataSeedContributor,在 CreateApplicationAsync 方法中将 OpenIddictConstants.GrantTypes.RefreshToken 添加到授权类型中:

await CreateApplicationAsync(
    ...
    grantTypes: new List<string> //混合流
    {
        OpenIddictConstants.GrantTypes.AuthorizationCode,
        OpenIddictConstants.GrantTypes.Implicit,
        OpenIddictConstants.GrantTypes.RefreshToken,
    },
    ...

注意: 如果您已经生成了数据库,则需要重新创建此客户端。

配置应用程序

您需要请求 offline_access scope 才能接收 refresh_token

Razor/MVC、Blazor-Server 应用程序中,将 options.Scope.Add("offline_access"); 添加到 OpenIdConnect 选项中。这些应用程序模板默认使用 cookie 身份验证,并且默认的 cookie 过期选项设置为:

.AddCookie("Cookies", options =>
{
    options.ExpireTimeSpan = TimeSpan.FromDays(365);
})

Cookie ExpireTimeSpan 会忽略 access_token 的过期时间,如果其设置的值高于 refresh_token 生命周期,那么过期的 access_token 仍然有效。建议保持 Cookie ExpireTimeSpan刷新令牌生命周期 相同,这样新令牌将被持久化到 cookie 中。

Blazor wasm 应用程序中,将 options.ProviderOptions.DefaultScopes.Add("offline_access"); 添加到 AddOidcAuthentication 选项中。

Angular 应用程序中,将 offline_access 添加到 environment.ts 文件中的 oAuthConfig 范围中。(Angular 应用程序已具备此配置)。

关于本地化

我们在 OpenIddict 模块中不本地化任何错误消息,因为 OAuth 2.0 规范限制了可用于错误参数和 error_description 参数的字符集:

A.7. "error" 语法 "error" 元素在第 4.1.2.1、4.2.2.1、5.2、7.2 和 8.5 节中定义:

error = 1*NQSCHAR

A.8. "error_description" 语法 "error_description" 元素在第 4.1.2.1、4.2.2.1、5.2 和 7.2 节中定义:

error-description = 1*NQSCHAR
NQSCHAR = %x20-21 / %x23-5B / %x5D-7E

演示项目

在模块的 app 目录中有六个项目(包括 angular

  • OpenIddict.Demo.Server:一个集成了模块的 abp 应用程序(有两个 clients 和一个 scope)。
  • OpenIddict.Demo.API:使用 JwtBearer 认证的 ASP.NET Core API 应用程序。
  • OpenIddict.Demo.Client.Mvc:使用 OpenIdConnect 进行认证的 ASP.NET Core MVC 应用程序。
  • OpenIddict.Demo.Client.Console:使用 IdentityModel 测试 OpenIddict 的各种端点,并调用 OpenIddict.Demo.API 的 API。
  • OpenIddict.Demo.Client.BlazorWASM:使用 OidcAuthentication 进行认证的 ASP.NET Core Blazor 应用程序。
  • angular:一个集成了 abp ng 模块并使用 oauth 进行认证的 Angular 应用程序。

如何运行?

确认 OpenIddict.Demo.Server 项目中 appsettings.json 的连接字符串。运行项目将自动创建数据库并初始化数据。 运行 OpenIddict.Demo.API 项目后,然后您可以运行其余项目进行测试。

迁移指南

从 IdentityServer 逐步迁移到 OpenIddict 指南

在本文档中