项目
版本

创建您自己的服务器实例

要使用 OpenIddict 实现自定义的 OpenID Connect 服务器,最简单的办法是克隆 openiddict-samples 仓库 中的一个官方示例。

如果您不想从推荐的示例之一开始,您需要做的是:

  • 重用现有项目或创建新项目:当使用 Visual Studio 的默认 ASP.NET Core 模板创建新项目时,强烈建议选择基于个人用户账户的认证,因为这会自动包含基于 Razor Pages 的默认 ASP.NET Core Identity 用户界面。这样,如果您稍后需要实现如授权码流之类的用户认证流程,将更加方便。

  • 更新您的 .csproj 文件,以引用最新版本的 OpenIddict.AspNetCoreOpenIddict.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-MigrationUpdate-Database 命令来更新数据库以包含 OpenIddict 相关表。

  • 使用 Postman 测试您的服务器实现:

    OAuth 2.0 client credentials grant with Postman

推荐阅读: 在您的 API 中实施令牌验证

在本文档中