ASP.NET Core 最小 API:极简开发,高效构建(下)

在上篇文章 ASP.NET Core 最小 API:极简开发,高效构建(上) 中我们添加了 API 代码并且测试,本篇继续补充相关内容。

一、使用 MapGroup API

示例应用代码每次设置终结点时都会重复 todoitems URL 前缀。 API 通常具有带常见 URL 前缀的终结点组,并且 MapGroup 方法可用于帮助组织此类组。 它减少了重复代码,并允许通过对 RequireAuthorizationWithMetadata 等方法的单一调用来自定义整个终结点组。

将 Program.cs 的内容替换为以下代码:

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using NSwag.AspNetCore;
using TodoApi;


var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
    config.DocumentName = "TodoAPI";
    config.Title = "TodoAPI v1";
    config.Version = "v1";
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi(config =>
    {
        config.DocumentTitle = "TodoAPI";
        config.Path = "/swagger";
        config.DocumentPath = "/swagger/{documentName}/swagger.json";
        config.DocExpansion = "list";
    });
}

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", async (TodoDb db) =>
    await db.Todos.ToListAsync());

todoItems.MapGet("/complete", async (TodoDb db) =>
    await db.Todos.Where(t => t.IsComplete).ToListAsync());

todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(todo)
            : Results.NotFound());

todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

前面的代码执行以下更改:

  • 添加 var todoItems = app.MapGroup("/todoitems"); 以使用 URL 前缀 /todoitems 设置组。
  • 将所有 app.Map<HttpVerb> 方法更改为 todoItems.Map<HttpVerb>
  • /todoitems 方法调用中移除 URL 前缀 Map<HttpVerb>

二、使用 TypedResults API

返回 TypedResults(而不是 Results)有几个优点,包括可测试性和自动返回 OpenAPI 的响应类型元数据来描述终结点。有关详细信息,请参阅 TypedResults 与 Results

使用以下代码更新 Program.cs:

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using NSwag.AspNetCore;
using TodoApi;


var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
    config.DocumentName = "TodoAPI";
    config.Title = "TodoAPI v1";
    config.Version = "v1";
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi(config =>
    {
        config.DocumentTitle = "TodoAPI";
        config.Path = "/swagger";
        config.DocumentPath = "/swagger/{documentName}/swagger.json";
        config.DocExpansion = "list";
    });
}

var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

app.Run();


static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

Map<HttpVerb> 代码现在调用方法,而不是 lambda:

csharp 复制代码
var todoItems = app.MapGroup("/todoitems");

todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);

这些方法返回实现 IResult 并由 TypedResults 定义的对象:

bash 复制代码
static async Task<IResult> GetAllTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.ToArrayAsync());
}

static async Task<IResult> GetCompleteTodos(TodoDb db)
{
    return TypedResults.Ok(await db.Todos.Where(t => t.IsComplete).ToListAsync());
}

static async Task<IResult> GetTodo(int id, TodoDb db)
{
    return await db.Todos.FindAsync(id)
        is Todo todo
            ? TypedResults.Ok(todo)
            : TypedResults.NotFound();
}

static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}

static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return TypedResults.NotFound();

    todo.Name = inputTodo.Name;
    todo.IsComplete = inputTodo.IsComplete;

    await db.SaveChangesAsync();

    return TypedResults.NoContent();
}

static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }

    return TypedResults.NotFound();
}

三、防止过度发布

目前,示例应用公开了整个 Todo 对象。 在生产应用中,通常使用模型的一个子集来限制可以输入和返回的数据。 这背后有多种原因,但安全性是主要原因。 模型的子集通常称为数据传输对象 (DTO)、输入模型或视图模型。 本文使用的是 DTO。

DTO 可以用于:

  • 防止过度发布。
  • 隐藏客户端不应查看的属性。
  • 省略某些属性以减少有效负载大小。
  • 平展包含嵌套对象的对象图。 对客户端而言,平展的对象图可能更方便。

更新 Todo 类,使其包含机密字段:

bash 复制代码
public class Todo
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
    public string? Secret { get; set; }
}

此应用需要隐藏机密字段,但管理应用可以选择公开它。使用以下代码创建名为 TodoItemDTO.cs 的文件:

csharp 复制代码
public class TodoItemDTO
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }

    public TodoItemDTO() { }
    public TodoItemDTO(Todo todoItem) =>
    (Id, Name, IsComplete) = (todoItem.Id, todoItem.Name, todoItem.IsComplete);
}

Program.cs 文件的内容替换为以下代码以使用此 DTO 模型:

bash 复制代码
using Microsoft.EntityFrameworkCore;
using NSwag.AspNetCore;
using TodoApi;


var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt => opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument(config =>
{
    config.DocumentName = "TodoAPI";
    config.Title = "TodoAPI v1";
    config.Version = "v1";
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseOpenApi();
    app.UseSwaggerUi(config =>
    {
        config.DocumentTitle = "TodoAPI";
        config.Path = "/swagger";
        config.DocumentPath = "/swagger/{documentName}/swagger.json";
        config.DocExpansion = "list";
    });
}

app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.Select(x => new TodoItemDTO(x)).ToListAsync());

app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
    await db.Todos.FindAsync(id)
        is Todo todo
            ? Results.Ok(new TodoItemDTO(todo))
            : Results.NotFound());

app.MapPost("/todoitems", async (TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todoItem = new Todo
    {
        IsComplete = todoItemDTO.IsComplete,
        Name = todoItemDTO.Name
    };

    db.Todos.Add(todoItem);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todoItem.Id}", new TodoItemDTO(todoItem));
});

app.MapPut("/todoitems/{id}", async (int id, TodoItemDTO todoItemDTO, TodoDb db) =>
{
    var todo = await db.Todos.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = todoItemDTO.Name;
    todo.IsComplete = todoItemDTO.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
    if (await db.Todos.FindAsync(id) is Todo todo)
    {
        db.Todos.Remove(todo);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});

app.Run();

运行效果,

四、配置 JSON 序列化选项

1、全局配置 JSON 序列化选项

可通过调用 ConfigureHttpJsonOptions 来全局配置应用的选项。 以下示例包含公共字段,并设置 JSON 输出的格式。

csharp 复制代码
var builder = WebApplication.CreateBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options => {
    options.SerializerOptions.WriteIndented = true;
    options.SerializerOptions.IncludeFields = true;
});

var app = builder.Build();

app.MapPost("/", (Todo todo) => {
    if (todo is not null) {
        todo.Name = todo.NameField;
    }
    return todo;
});

app.Run();

class Todo {
    public string? Name { get; set; }
    public string? NameField;
    public bool IsComplete { get; set; }
}

运行效果:

当请求报文为

bash 复制代码
{
  "name": "test",
  "isComplete": true
}

则返回

bash 复制代码
{
  "name": null,
  "isComplete": true,
  "nameField": null
}

当请求报文为

bash 复制代码
{
  "nameField": "Walk dog",
  "isComplete": false
}

则返回

bash 复制代码
{
  "name": "Walk dog",
  "isComplete": false,
  "nameField": "Walk dog"
}

2、为终结点配置 JSON 序列化选项

若要为终结点配置序列化选项,请调用 Results.Json 并向其传递 JsonSerializerOptions 对象,如以下示例所示:

bash 复制代码
using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
    { WriteIndented = true };

app.MapGet("/", () => 
    Results.Json(new Todo { Name = "Walk dog", IsComplete = false }, options));

app.Run();

class Todo
{
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

运行效果,

或者,使用接受 JsonSerializerOptions 对象的 WriteAsJsonAsync 的重载。 以下示例使用此重载设置输出 JSON 的格式:

bash 复制代码
using System.Text.Json;

var app = WebApplication.Create();

var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) {
    WriteIndented = true };

app.MapGet("/", (HttpContext context) =>
    context.Response.WriteAsJsonAsync<Todo>(
        new Todo { Name = "Walk dog", IsComplete = false }, options));

app.Run();

class Todo
{
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

运行效果:

五、最小 API 中的身份验证和授权

1、有关身份验证和授权的关键概念

身份验证是确定用户标识的过程。 授权是确定用户是否有权访问资源的过程。 在 ASP.NET Core 中,身份验证和授权方案都有类似的实现语义。 身份验证由身份验证服务 IAuthenticationService 负责,而它供身份验证中间件使用。 授权由授权服务 IAuthorizationService 负责,而它供授权中间件使用。

身份验证服务会使用已注册的身份验证处理程序来完成与身份验证相关的操作。 例如,与身份验证相关的操作是对用户进行身份验证或注销用户。 身份验证方案是用于唯一地标识身份验证处理程序及其配置选项的名称。 身份验证处理程序负责实现身份验证策略,并在给定特定身份验证策略(如 OAuth 或 OIDC)的情况下生成用户的声明。 配置选项也是策略独有的,并为处理程序提供会影响身份验证行为的配置,例如重定向 URI。

在授权层中,有两种策略可用于确定用户对资源的访问权限:

  • 基于角色的策略根据所分配的角色(例如 Administrator 或 User)确定用户的访问权限。
  • 基于声明的策略根据中央颁发机构颁发的声明来确定用户的访问权限。

ASP.NET Core 中,这两种策略都被捕获到授权要求中。 授权服务利用授权处理程序来确定特定用户是否满足应用于资源的授权要求。

2、在最小应用中启用身份验证

若要启用身份验证,请调用 AddAuthentication 以对应用的服务提供商注册所需的身份验证服务。

bash 复制代码
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication();
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.Run();

通常情况下,会使用一个特定的身份验证策略。 在以下示例中,应用被配置为支持基于 JWT 持有者的身份验证。 此示例使用 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包中提供的 API。

bash 复制代码
var builder = WebApplication.CreateBuilder(args);
// Requires Microsoft.AspNetCore.Authentication.JwtBearer
builder.Services.AddAuthentication().AddJwtBearer();
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.Run();

默认情况下,如果启用了某些身份验证和授权服务,WebApplication 会自动注册身份验证和授权中间件。 在以下示例中,无需调用 UseAuthenticationUseAuthorization 即可注册中间件,因为在调用 WebApplicationAddAuthentication 后,AddAuthorization 会自动执行此操作。

bash 复制代码
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.Run();

具体原理可以查阅最小 API 应用中的中间件。在某些情况(例如控制中间件顺序)下,需要显式注册身份验证和授权。 在以下示例中,身份验证中间件是在 CORS 中间件运行后运行的。

bash 复制代码
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors();
builder.Services.AddAuthentication().AddJwtBearer();
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseCors();
app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/", () => "Hello World!");
app.Run();

3、配置身份验证策略

身份验证策略通常支持通过选项加载的各种配置。 对于以下身份验证策略,最小应用支持从配置加载选项:

  • 基于 JWT 持有者
  • 基于 OpenID 连接

ASP.NET Core 框架期望在Authentication:Schemes:{SchemeName}节的配置部分下找到这些选项。 在以下示例中,两个不同的方案 BearerLocalAuthIssuer 是使用各自的选项定义的。 Authentication:DefaultScheme 选项可用于配置所使用的默认身份验证策略。

csharp 复制代码
{
  "Authentication": {
    "DefaultScheme":  "LocalAuthIssuer",
    "Schemes": {
      "Bearer": {
        "ValidAudiences": [
          "https://localhost:7259",
          "http://localhost:5259"
        ],
        "ValidIssuer": "dotnet-user-jwts"
      },
      "LocalAuthIssuer": {
        "ValidAudiences": [
          "https://localhost:7259",
          "http://localhost:5259"
        ],
        "ValidIssuer": "local-auth"
      }
    }
  }
}

Program.cs 中,注册了两个基于 JWT 持有者的身份验证策略,其中:

  • "Bearer"方案名称。
  • "LocalAuthIssuer"方案名称。

"Bearer"是支持基于 JWT 持有者的应用中的一个典型默认方案,但可以通过设置 DefaultScheme 属性来替代默认方案,如前面的示例所示。

方案名称用于唯一地标识身份验证策略,并在从配置解析身份验证选项时用作查找键,如以下示例所示:

csharp 复制代码
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication()
  .AddJwtBearer()
  .AddJwtBearer("LocalAuthIssuer");
  
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.Run();

4、在最小应用中配置授权策略

身份验证用于根据 API 识别和验证用户的标识。 授权用于验证和证实对 API 中资源的访问,并由通过 AddAuthorization 扩展方法注册的 IAuthorizationService 提供便利。 在以下方案中,添加了 /hello 资源,该资源要求用户提供一个 admin 角色声明以及 greetings_api 范围声明。

配置资源上的授权要求的过程分为两个步骤,需要:

  1. 全局配置策略中的授权要求。
  2. 将各个策略应用于资源。

在以下代码中,将调用 AddAuthorizationBuilder,其作用是:

  • 将与授权相关的服务添加到 DI 容器。
  • 返回一个 AuthorizationBuilder,它可用于直接注册身份验证策略。

该代码创建了一个名为 admin_greetings 的新授权策略,该策略封装了两个授权要求:

  • 一个通过 RequireRole 实现的基于角色的要求,面向具有 admin 角色的用户。
  • 一个通过 RequireClaim 实现的基于声明的要求,即用户必须提供 greetings_api 范围声明。

admin_greetings 策略作为 /hello 终结点所需的策略提供。

bash 复制代码
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorizationBuilder()
  .AddPolicy("admin_greetings", policy =>
        policy
            .RequireRole("admin")
            .RequireClaim("scope", "greetings_api"));

var app = builder.Build();

app.MapGet("/hello", () => "Hello world!")
  .RequireAuthorization("admin_greetings");

app.Run();

若无权限,则直接返回 401 Unauthorized 错误。

六、使用 dotnet user-jwts 进行开发测试

本文使用配置了基于 JWT 持有者的身份验证的应用。 基于 JWT 令牌持有者的身份验证要求客户端在请求头中呈现令牌,以验证其身份和声明。 通常,这些令牌由中央颁发机构颁发,例如标识服务器。

在本地计算机上进行开发时,dotnet user-jwts 工具可用于创建持有者令牌。

bash 复制代码
dotnet user-jwts create

注意

在项目上调用时,该工具会自动将与生成的令牌匹配的身份验证选项添加到 appsettings.json

可以通过多种自定义方式配置令牌。 例如,若要为上述代码中的授权策略所需的 admin 角色和 greetings_api 范围创建令牌,请执行以下操作:

bash 复制代码
dotnet user-jwts create --scope "greetings_api" --role "admin"

然后,可以在所选的测试工具中将生成的令牌作为标头的一部分发送。 举个使用 curl 的例子:

bash 复制代码
curl -i -H "Authorization: Bearer {token}" http://localhost:{port}/hello
bash 复制代码
(base) sam@sam-PC:/data/home/sam/MyWorkSpace/DotNetCoreWorkSpace/TodoApi$ dotnet user-jwts create --scope "greetings_api" --role "admin"
New JWT saved with ID 'b0fafeb4'.
Name: sam
Roles: [admin]
Scopes: greetings_api

Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InNhbSIsInN1YiI6InNhbSIsImp0aSI6ImIwZmFmZWI0Iiwic2NvcGUiOiJncmVldGluZ3NfYXBpIiwicm9sZSI6ImFkbWluIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzQ0MiIsImh0dHBzOi8vbG9jYWxob3N0OjQ0MzgwIiwiaHR0cDovL2xvY2FsaG9zdDo1MDI2IiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE1MyJdLCJuYmYiOjE3NDUwNTcwNDcsImV4cCI6MTc1MjkxOTQ0NywiaWF0IjoxNzQ1MDU3MDQ4LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Rwgp9wO9PCLwJEiVgN-CGwlnLaAu0jhQXuzeo8Wh6Zg
bash 复制代码
curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InNhbSIsInN1YiI6InNhbSIsImp0aSI6ImIwZmFmZWI0Iiwic2NvcGUiOiJncmVldGluZ3NfYXBpIiwicm9sZSI6ImFkbWluIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzQ0MiIsImh0dHBzOi8vbG9jYWxob3N0OjQ0MzgwIiwiaHR0cDovL2xvY2FsaG9zdDo1MDI2IiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE1MyJdLCJuYmYiOjE3NDUwNTcwNDcsImV4cCI6MTc1MjkxOTQ0NywiaWF0IjoxNzQ1MDU3MDQ4LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Rwgp9wO9PCLwJEiVgN-CGwlnLaAu0jhQXuzeo8Wh6Zg" http://localhost:5026/hello

可以看到,携带 token 请求可以正常访问 /hello 接口。完整代码如下:

bash 复制代码
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();
builder.Services.AddAuthentication("Bearer").AddJwtBearer();

builder.Services.AddAuthorizationBuilder()
  .AddPolicy("admin_greetings", policy =>
        policy
            .RequireRole("admin")
            .RequireClaim("scope", "greetings_api"));

var app = builder.Build();

app.UseAuthorization();

app.MapGet("/hello", () => "Hello world!")
  .RequireAuthorization("admin_greetings");

app.Run();

参考文档

相关推荐
Starshime2 分钟前
【MySQL】MySQL的基础语法及其语句的介绍
数据库·mysql
MaCa .BaKa12 分钟前
33-公交车司机管理系统
java·vue.js·spring boot·maven
江沉晚呤时30 分钟前
如何在 .NET 环境中使用 Npgsql 驱动连接 KaiwuDB
数据库·sql·oracle
洛小豆38 分钟前
一个场景搞明白Reachability Fence,它就像一道“结账前别走”的红外感应门
java·后端·面试
500佰40 分钟前
AI提示词(Prompt)设计优化方案 | 高效使用 AI 工具
java·人工智能·prompt·ai编程
摘星编程41 分钟前
并发设计模式实战系列(4):线程池
java·设计模式·并发编程
PGCCC1 小时前
【PGCCC】Postgres MVCC 内部:更新与插入的隐性成本
java·开发语言·数据库
随心~稳心1 小时前
MongoDB导出和导入数据
数据库·mongodb
HtwHUAT1 小时前
十、数据库day02--SQL语句01
数据库·sql·mysql·oracle
YGGP1 小时前
【每日八股】复习 MySQL Day1:事务
数据库·mysql