创建您自己的服务器实例
要使用 OpenIddict 实现自定义的 OpenID Connect 服务器,最简单的办法是克隆 openiddict-samples 仓库 中的一个官方示例。
如果您不想从推荐的示例之一开始,您需要做的是:
重用现有项目或创建新项目:当使用 Visual Studio 的默认 ASP.NET Core 模板创建新项目时,强烈建议选择基于个人用户账户的认证,因为这会自动包含基于 Razor Pages 的默认 ASP.NET Core Identity 用户界面。这样,如果您稍后需要实现如授权码流之类的用户认证流程,将更加方便。
更新您的
.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
)中注册您的 Entity Framework Core 数据库上下文,并配置 OpenIddict 核心服务:services.AddDbContext<ApplicationDbContext>(options => { // 配置 EfCore 使用 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 服务器服务:
services.AddOpenIddict() // 注册 OpenIddict 服务器组件 .AddServer(options => { // 启用 token 端点 options.SetTokenEndpointUris("connect/token"); // 启用客户端凭据流 options.AllowClientCredentialsFlow(); // 注册签名和加密凭证 options.AddDevelopmentEncryptionCertificate() .AddDevelopmentSigningCertificate(); // 注册 ASP.NET Core 主机并且配置 ASP.NET Core 选项 options.UseAspNetCore() .EnableTokenEndpointPassthrough(); });
确保 ASP.NET Core 认证中间件在正确的位置被恰当地注册:
app.UseDeveloperExceptionPage(); app.UseRouting(); app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(options => { options.MapControllers(); options.MapDefaultControllerRoute(); });
创建您自己的授权控制器:实现自定义的授权控制器是必要的,以便 OpenIddict 能够根据您提供的身份和声明来创建令牌。以下是一个针对客户端凭据授权类型的示例:
public class AuthorizationController : Controller { private readonly IOpenIddictApplicationManager _applicationManager; public AuthorizationController(IOpenIddictApplicationManager applicationManager) => _applicationManager = applicationManager; [HttpPost("~/connect/token"), Produces("application/json")] public async Task<IActionResult> Exchange() { var request = HttpContext.GetOpenIddictServerRequest(); if (request.IsClientCredentialsGrantType()) { // 注意:客户端凭证会自动由OpenIddict进行验证: // 如果client_id或client_secret无效,此操作将不会被调用 var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? throw new InvalidOperationException("The application cannot be found."); // 创建一个新的ClaimsIdentity,其中包含用于生 // 成 id_token、token 或 code的声明. var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, Claims.Name, Claims.Role); // 使用 client_id 作为主题标识符 identity.SetClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application)); identity.SetClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application)); identity.SetDestinations(static claim => claim.Type switch { // 当授予了 “profile” 范围时,允许 “name” 声明被存储在 // 访问令牌和身份令牌中 (通过调用 principal.SetScopes(...)) Claims.Name when claim.Subject.HasScope(Scopes.Profile) => [Destinations.AccessToken, Destinations.IdentityToken], // 否则,仅将该声明存储在访问令牌中 _ => [Destinations.AccessToken] }); return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); } throw new NotImplementedException("The specified grant is not implemented."); } }
注册您的客户端应用程序(例如,使用 IHostedService 实现):
public class Worker : IHostedService { private readonly IServiceProvider _serviceProvider; public Worker(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; public async Task StartAsync(CancellationToken cancellationToken) { using var scope = _serviceProvider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); await context.Database.EnsureCreatedAsync(); var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>(); if (await manager.FindByClientIdAsync("service-worker") is null) { await manager.CreateAsync(new OpenIddictApplicationDescriptor { ClientId = "service-worker", ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207", Permissions = { Permissions.Endpoints.Token, Permissions.GrantTypes.ClientCredentials } }); } } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; }
在运行应用程序之前,请确保通过执行
Add-Migration
和Update-Database
命令来更新数据库以包含 OpenIddict 相关表。使用 Postman 测试您的服务器实现:
推荐阅读: 在您的 API 中实施令牌验证