与远程服务器实例集成
OpenIddict 客户端是一个通用的 OAuth 2.0/OpenID Connect .NET 客户端,可用于 Web 应用程序(要求 ASP.NET 4.6.1+ 或 ASP.NET Core 2.1+)或桌面应用程序(要求 .NET 4.6.1+ 或 .NET 6.0+)。
大多数设置都适用于 Web 和桌面应用程序,但像授权码流或隐式流这样的交互式流程需要根据应用程序类型进行特定的集成:
在任何 .NET 应用程序中实现非交互式 OAuth 2.0 客户端:
像资源拥有者密码凭证(ROPC)或客户端凭证这样的非交互式流程在 Web 和桌面应用程序中的实现方式相同。如果您想使用像客户端凭证流这样的非交互式流程,您需要:
拥有现有项目或创建新项目:推荐使用 .NET 通用主机,但对于非交互式流程不是强制性的。无论如何,您都需要使用依赖注入(
Microsoft.Extensions.DependencyInjection
或其他 DI 容器)。更新您的
.csproj
文件 以引用最新版本的OpenIddict
包:<PackageReference Include="OpenIddict" Version="5.6.0" />
在
Program.cs
配置 OpenIddict 客户端服务 (或Startup.cs
如果您使用常规的 ASP.NET Core Web 主机)services.AddOpenIddict() // 注册 OpenIddict 客户端组件 .AddClient(options => { // 允许 grant_type=client_credentials 以进行协商 options.AllowClientCredentialsFlow(); // 禁用令牌存储,这对于非交互式流(如客户端凭证流)是不必要的 // grant_type=password, grant_type=client_credentials or grant_type=refresh_token. options.DisableTokenStorage(); // 注册 System.Net.Http 集成 options.UseSystemNetHttp(); // 添加包含服务器颁发的客户端标识符和密钥的客户端注册. options.AddRegistration(new OpenIddictClientRegistration { Issuer = new Uri("https://localhost:44385/", UriKind.Absolute), ClientId = "service-worker", ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207" }); });
使用
OpenIddictClientService
从远程服务器获取访问令牌var service = provider.GetRequiredService<OpenIddictClientService>(); var result = await service.AuthenticateWithClientCredentialsAsync(new()); var token = result.AccessToken;
在 ASP.NET Core 应用程序中实现交互式 OAuth 2.0/OpenID Connect 客户端:
要在 ASP.NET Core 应用程序中实现交互式 OAuth 2.0/OpenID Connect 客户端,最简单的选择是克隆 openiddict-samples 存储库 中的官方示例之一。
如果您不想从推荐的示例之一开始,您需要:
拥有现有项目或创建新项目:使用 Visual Studio 的默认 ASP.NET Core 模板创建新项目时,强烈建议使用个人用户账户认证,因为它会自动包含基于 Razor Pages 的默认 ASP.NET Core Identity UI,该 UI 以透明的方式自动处理用户创建过程或外部登录集成。
更新您的
.csproj
文件 以引用最新的OpenIddict.AspNetCore
和OpenIddict.EntityFrameworkCore
包:<PackageReference Include="OpenIddict.AspNetCore" Version="5.6.0" /> <PackageReference Include="OpenIddict.EntityFrameworkCore" Version="5.6.0" />
在
Program.cs
(或根据您是使用最小化主机还是常规主机而在Startup.cs
)中配置 OpenIddict 核心服务:配置数据库是必需的,因为 OpenIddict 客户端默认情况下是有状态的:它使用IOpenIddictTokenStore
服务来存储其创建的状态令牌的有效负载——以保护回调阶段免受 CSRF/会话固定攻击——并启用状态令牌的自动兑换(与 ASP.NET Core OAuth 2.0 或 OpenID Connect 处理程序不同,OpenIddict 客户端防止状态令牌被多次使用,以减轻授权响应/状态令牌重放攻击)。services.AddDbContext<ApplicationDbContext>(options => { // 配置Entity Framework Core以使用Microsoft SQL Server options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); // 注册 OpenIddict 所需的实体集. // 注意:如果您需要替换默认的OpenIddict实体,请使用泛型重载 options.UseOpenIddict(); });
services.AddOpenIddict() // 注册OpenIddict的核心组件 .AddCore(options => { // 配置OpenIddict使用Entity Framework Core存储和模型 // 注意:调用ReplaceDefaultEntities()以替换默认实体 options.UseEntityFrameworkCore() .UseDbContext<ApplicationDbContext>(); });
配置 OpenIddict 客户端服务。以下是一个启用代码流支持并添加 GitHub 集成的示例:
services.AddOpenIddict() // 注册OpenIddict客户端组件 .AddClient(options => { // 注意:此示例仅使用授权码流,但如有需要,您可以启用其他流 options.AllowAuthorizationCodeFlow(); // 注册用于保护敏感数据(如OpenIddict生成的状态令牌)的签名和加密凭据。 options.AddDevelopmentEncryptionCertificate() .AddDevelopmentSigningCertificate(); // 注册ASP.NET Core主机并配置ASP.NET Core特定的选项 options.UseAspNetCore() .EnableRedirectionEndpointPassthrough(); // 注册System.Net.Http集成 options.UseSystemNetHttp(); // 注册Web提供商集成 // // 注意:为了缓解混合攻击,建议为每个提供商使用唯一的重定向端点URI, // 除非所有注册的提供商都支持在授权响应中返回包含其URL的特殊"iss"参数。 // 更多信息,查看: // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4. options.UseWebProviders() .AddGitHub(options => { options.SetClientId("c4ade52327b01ddacff3") .SetClientSecret("da6bed851b75e317bf6b2cb67013679d9467c122") .SetRedirectUri("callback/login/github"); }); });
确保 ASP.NET Core 身份验证中间件在正确的位置正确注册:
app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(options => { options.MapControllers(); options.MapDefaultControllerRoute(); });
添加一个负责处理 OAuth 2.0/OpenID Connect 回调的认证控制器:
[HttpGet("~/callback/login/{provider}"), HttpPost("~/callback/login/{provider}"), IgnoreAntiforgeryToken] public async Task<ActionResult> LogInCallback() { // 在处理回调过程中,检索由OpenIddict验证的授权数据 var result = await HttpContext.AuthenticateAsync(OpenIddictClientAspNetCoreDefaults.AuthenticationScheme); // 重要提示:如果远程服务器不支持 OpenID Connect 且未暴露用户信息端点,result.Principal.Identity 将表示一个未验证 // 的身份,并且不会包含任何用户声明。 // // 这样的身份不能直接用于在 ASP.NET Core 中构建认证 cookie(因为反伪造堆栈至少需要一个名称声明来将CSRF cookie绑定到 // 用户的标识),但可以通过 result.Properties.GetTokens() 获取访问令牌和刷新令牌,以便进行API调用。 if (result.Principal is not ClaimsPrincipal { Identity.IsAuthenticated: true }) { throw new InvalidOperationException("The external authorization data cannot be used for authentication."); } // 根据外部声明构建一个身份,该身份将用于创建认证 cookie var identity = new ClaimsIdentity(authenticationType: "ExternalLogin"); // 默认情况下,OpenIddict会尝试自动从标准OpenID Connect或特定于提供商的等效项中映射电子邮件/姓名和名称 // 标识符声明(如果可用)。如有需要,可以从外部身份解析额外的声明,并复制到最终的认证cookie中。 identity.SetClaim(ClaimTypes.Email, result.Principal.GetClaim(ClaimTypes.Email)) .SetClaim(ClaimTypes.Name, result.Principal.GetClaim(ClaimTypes.Name)) .SetClaim(ClaimTypes.NameIdentifier, result.Principal.GetClaim(ClaimTypes.NameIdentifier)); // 保留注册标识符,以便稍后解析 identity.SetClaim(Claims.Private.RegistrationId, result.Principal.GetClaim(Claims.Private.RegistrationId)); // 根据触发挑战时添加的属性构建认证属性 var properties = new AuthenticationProperties(result.Properties.Items) { RedirectUri = result.Properties.RedirectUri ?? "/" }; // 如果需要,授权服务器返回的令牌可以存储在认证cookie中 // // 为了减小cookie的大小,创建cookie之前会过滤掉未使用的令牌 properties.StoreTokens(result.Properties.GetTokens().Where(token => token.Name is // 如果可用,保留令牌响应中返回的访问、身份和刷新令牌 OpenIddictClientAspNetCoreConstants.Tokens.BackchannelAccessToken or OpenIddictClientAspNetCoreConstants.Tokens.BackchannelIdentityToken or OpenIddictClientAspNetCoreConstants.Tokens.RefreshToken)); // 请求默认的登录处理器返回一个新的cookie,并将用户代理重定向到存储在认证属性中的回退URL // // 对于不应使用ASP.NET Core认证选项中配置的默认登录处理器的场景,可以在此处指定特定的方案 return SignIn(new ClaimsPrincipal(identity), properties); }
一旦正确配置,GitHub 提供商应该出现在支持的外部提供商列表中:
在桌面应用程序中实现交互式 OAuth 2.0/OpenID Connect 客户端:
要在桌面应用程序中实现交互式 OAuth 2.0/OpenID Connect 客户端,最简单的选项是克隆 openiddict-samples 存储库 中的官方示例之一。
有关如何在 Linux 和 Windows 上使用 OpenIddict.Client.SystemIntegration
包的更多信息,请阅读 介绍 OpenIddict 客户端的系统集成支持