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 =>
{
//在此设置选项...
});
}
这些服务包括:
- 添加
ApplicationStore、AuthorizationStore、ScopeStore、TokenStore。 - 替换
ApplicationManager、AuthorizationManager、ScopeManager、TokenManager。 - 替换
ApplicationStoreResolver、AuthorizationStoreResolver、ScopeStoreResolver、TokenStoreResolver。 - 设置
DefaultApplicationEntity、DefaultAuthorizationEntity、DefaultScopeEntity、DefaultTokenEntity。
OpenIddictServerBuilder
OpenIddictServerBuilder 包含用于配置 OpenIddict 服务器服务的扩展方法。
示例:
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictServerBuilder>(builder =>
{
//在此设置选项...
});
}
这些服务包括:
- 注册声明、范围。
- 设置
IssuerURI,该 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 存储:
IAbpOpenIdApplicationStoreIOpenIddictAuthorizationStoreIOpenIddictScopeStoreIOpenIddictTokenStore
AbpOpenIddictStoreOptions
您可以配置 AbpOpenIddictStoreOptions 的 PruneIsolationLevel/DeleteIsolationLevel 来设置存储操作的事务隔离级别,因为不同的数据库有不同的隔离级别。
存储库
此模块中定义了以下自定义存储库:
IOpenIddictApplicationRepositoryIOpenIddictAuthorizationRepositoryIOpenIddictScopeRepositoryIOpenIddictTokenRepository
领域服务
此模块不包含任何领域服务,但重写了以下服务:
AbpApplicationManager用于填充/获取包含ClientUri和LogoUri的AbpApplicationDescriptor信息。
数据库提供程序
通用
表/集合前缀与架构
默认情况下,所有表/集合都使用 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 服务将默认将 Name、Email 和 Role 类型的声明添加到 access_token 和 id_token 中,其他声明默认仅添加到 access_token,并移除 Identity 的 SecurityStampClaimType 秘密声明。
创建一个继承自 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;
});
请求/响应过程
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,验证请求参数(client、scope 等)是否有效并存在于数据库中,等等。进行各种协议检查。并构建一个 OpenIddictRequest 对象。如果有任何错误,可能会设置响应内容并直接短路当前请求。
如果一切正常,请求将转到我们的处理控制器(例如 TokenController),此时我们可以从 HTTP 请求中获取 OpenIddictRequest 对象。其余处理将基于此对象进行。
检查请求中的 username 和 password。如果正确,则创建一个 ClaimsPrincipal 对象并返回一个 SignInResult,它使用 OpenIddict.Validation.AspNetCore 认证方案名称,将调用 OpenIddictServerAspNetCoreHandler 进行处理。
OpenIddictServerAspNetCoreHandler 进行一些检查以生成 JSON 并替换 HTTP 响应内容。
ForbidResult 和 ChallengeResult 的处理方式同上。
如果您需要自定义 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 项目后,然后您可以运行其余项目进行测试。
抠丁客


