.NET 7 Preview 5 现已推出,其中包括对 ASP.NET Core 的许多重大新改进。
以下是此预览版中新增功能的摘要:
有关为 .NET 7 计划的 ASP.NET Core 工作的更多详细信息,请参阅GitHub 上的 .NET 7 的完整 ASP.NET Core 路线图。
要开始使用 .NET 7 Preview 5 中的 ASP.NET Core,请安装 .NET 7 SDK。
如果你在 Windows 上使用 Visual Studio,我们建议安装最新的Visual Studio 2022 预览版。如果您使用的是 macOS,我们建议您安装最新的Visual Studio 2022 for Mac 预览版。
要安装最新的 .NET WebAssembly 构建工具,请从提升的命令提示符处运行以下命令:
dotnet workload install wasm-tools注意:目前不支持使用 .NET 7 SDK 和 .NET 7 WebAssembly 构建工具构建 .NET 6 Blazor 项目。这将在未来的 .NET 7 更新中解决:dotnet/runtime#65211。
要将现有 ASP.NET Core 应用从 .NET 7 Preview 4 升级到 .NET 7 Preview 5:
另请参阅.NET 7 的 ASP.NET Core中的重大更改的完整列表。
如今,为 ASP.NET Core 应用程序配置身份验证 (AuthN) 和授权 (AuthZ) 需要进行大量更改,包括添加和配置服务,以及在应用程序启动过程的不同阶段添加中间件。我们收到反馈,用户发现配置身份验证和授权是使用 ASP.NET Core 构建 API 的最困难的事情之一。鉴于正确配置身份验证和授权对于保护 Web 应用程序的安全至关重要,我们进行了一些改进,旨在简化 ASP.NET Core 该领域最常见的方面,最初的重点是 JWT 不记名身份验证,这是常用的保护 Web API。
现在可以直接从应用程序的配置系统自动配置身份验证选项,因为在通过新Authentication属性配置身份验证时添加了默认配置部分,WebApplicationBuilder如下所示:
var builder = WebApplication.Create(args);
builder.Authentication.AddJwtBearer(); // New top-level property for setting up authentication
var app = builder.Build();这个新属性在您的应用程序代码中提供了一个设置身份验证的中心位置,提供对实例的轻松访问,AuthenticationBuilder可以从中添加和配置身份验证方案。通过这个新属性设置身份验证还将自动将所需的中间件添加到请求管道中,类似于WebApplicationBuilder已经为路由执行此操作的方式。
下面是一个使用 JWT 不记名身份验证和两个端点的应用设置示例,一个需要授权,一个不需要:
var builder = WebApplication.Create(args);
builder.Authentication.AddJwtBearer();
var app = builder.Build();
app.MapGet("/", () => "Hello, World!");
app.MapGet("/secret", (ClaimsPrincipal user) => #34;Hello {user.Identity?.Name}. This is a secret!")
.RequireAuthorization();
app.Run();此外,各个身份验证方案可以从应用程序的配置中自动设置其选项,从而更容易在不同环境(例如本地开发与生产)之间配置它们。对于此版本,仅更新了 JWT 承载方案以支持此机制,但我们将更新更多身份验证方案以支持此机制。
这是应用程序文件的示例,已更新以通过新部分设置身份验证选项:appsettings.Development.json"Authentication"
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Authentication": {
"DefaultScheme" : "JwtBearer",
"Schemes": {
"JwtBearer": {
"Audiences": [ "http://localhost:5000", "https://localhost:5001" ],
"ClaimsIssuer": "dotnet-user-jwts"
}
}
}
}请注意,仍然必须通过代码添加身份验证方案,才能通过新的配置部分设置它们的选项。
前面的示例包含一个仅允许经过身份验证的用户访问它的端点定义 ( )。但是,如果端点的授权要求稍微复杂一些,例如只允许具有特定“范围”声明的用户呢?一组授权要求在“策略”中定义,通常全局定义为在应用程序服务中设置授权的一部分,然后在配置端点时通过其名称引用。这有利于重用,但在某些情况下可能难以发现并且过于复杂。RequireAuthorization()
对于不需要在端点之间共享授权策略的情况,您现在可以通过元数据直接在端点上轻松定义授权策略,如下所示:
app.MapGet("/special-secret", () => "This is a special secret!")
.RequireAuthorization(p => p.RequireClaim("scope", "myapi:secrets"));现在我们拥有受 JWT 身份验证保护的端点,如果不需要完整的身份和用户管理服务,就可以轻松地验证它们在本地开发环境中的配置是否正确。为此,我们需要一些东西来发布 JWT 以在本地与我们的应用程序一起使用,这是新命令行工具的工作。dotnet user-jwts
如果我们尝试使用Postman、curl或之类的工具从前面的示例中访问受保护的端点dotnet httprepl,我们将收到带有 HTTP 401(未授权)状态代码的响应形式的错误,表明请求没有t 提供任何身份验证详细信息,因此未经授权访问该资源:
MyWebApi$ curl -i http://localhost:5000
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Tue, 07 Jun 2022 23:39:10 GMT
Server: Kestrel
Transfer-Encoding: chunked
Hello, World!
MyWebApi$ curl -i http://localhost:5000/secret
HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Tue, 07 Jun 2022 23:38:07 GMT
Server: Kestrel
WWW-Authenticate: Bearer
MyWebApi$由于应用程序配置为使用 JWT 不记名身份验证,我们需要在请求中提供 JWT。在完全部署的系统中,JWT 通常由充当安全令牌服务 (STS) 的服务器提供,可能是为了响应通过一组凭据登录。但是为了在本地开发期间使用我们的 API,我们可以使用新的命令行工具来创建和管理特定于应用程序的本地 JWT。dotnet user-jwts
该工具在概念上与现有工具相似,因为它可用于管理仅对当前机器上的当前用户(开发人员)有效的应用程序值。事实上,该工具利用基础设施来管理 JWT 将用来签名的密钥,确保它安全地存储在用户配置文件中。user-jwtsuser-secretsuser-jwtsuser-secrets
首先,我们必须通过调用和初始化我们项目的用户机密系统(请注意,这将在未来的预览版中自动为您完成):dotnet user-secrets initdotnet user-secrets listdotnet user-jwts
MyWebApi$ dotnet user-jwts create
Project does not contain a user secrets ID.
MyWebApi$ dotnet user-secrets init
Set UserSecretsId to 'b78e7e01-e648-421a-9ccd-e1a31dd58529' for MSBuild project 'MyWebApi.csproj'.
MyWebApi$ dotnet user-secrets list
No secrets configured for this application.现在我们可以使用该工具创建一个 JWT 以与我们的示例应用程序一起使用:user-jwts
MyWebApi$ dotnet user-jwts create
New JWT saved with ID '643a8abc'.
MyWebApi$ dotnet user-jwts print 643a8abc --show-full
Found JWT with ID 'b0498b94'
{
"Id": "b0498b94",
"Scheme": "Bearer",
"Name": "damia",
"Audience": "https://localhost:7188",
"NotBefore": "2022-06-08T00:03:31+00:00",
"Expires": "2022-09-08T00:03:31+00:00",
"Issued": "2022-06-08T00:03:31+00:00",
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM",
"Scopes": [],
"Roles": [],
"CustomClaims": {}
}
Token Header: {"alg":"HS256","typ":"JWT"}
Token Payload: {"unique_name":"damia","sub":"damia","jti":"b0498b94","aud":["https://localhost:7188","http://localhost:5056"],"nbf":1654646611,"exp":1662595411,"iat":1654646611,"iss":"dotnet-user-jwts"}
Compact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM
MyWebApi$ 该命令负责使用 JWT 不记名身份验证方案所需的配置值create更新我们项目的文件和用户机密存储,以识别用户 JWT。该命令打印出我们可以包含在请求标头中的 JWT 值,以测试受保护的 API(请注意,现在需要该选项来检索 JWT 值,但在未来的版本中,默认情况下会在使用and时显示该选项)。appsettings.Development.jsonprintAuthorization--show-fulluser-jwt createuser-jwt print
现在让我们再次尝试访问受保护的 API,这次使用工具创建的 JWT 值:
MyWebApi$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImRhbWlhIiwic3ViIjoiZGFtaWEiLCJqdGkiOiJiMDQ5OGI5NCIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0NjYxMSwiZXhwIjoxNjYyNTk1NDExLCJpYXQiOjE2NTQ2NDY2MTEsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.4lS34bXQdmubMf7JIpa6kSraVPpIe9nA-2Ptni2GdMM" http://localhost:5000/secret
Hello damian. This is a secret!
MyWebApi$ 您可以创建具有不同声明的 JWT,以探索和验证应用的授权配置。让我们使用自定义用户名创建和使用 JWT:
MyWebApi$ dotnet user-jwts create --name MyTestUser
New JWT saved with ID '5d285409'.
MyWebApi$ dotnet user-jwts print 5d285409 --show-full
Found JWT with ID '5d285409'
{
"Id": "5d285409",
"Scheme": "Bearer",
"Name": "MyTestUser",
"Audience": "https://localhost:7188",
"NotBefore": "2022-06-08T00:53:57+00:00",
"Expires": "2022-09-08T00:53:57+00:00",
"Issued": "2022-06-08T00:53:57+00:00",
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc",
"Scopes": [],
"Roles": [],
"CustomClaims": {}
}
Token Header: {"alg":"HS256","typ":"JWT"}
Token Payload: {"unique_name":"MyTestUser","sub":"MyTestUser","jti":"5d285409","aud":["https://localhost:7188","http://localhost:5056"],"nbf":1654649637,"exp":1662598437,"iat":1654649637,"iss":"dotnet-user-jwts"}
Compact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc
MyWebApi$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Ik15VGVzdFVzZXIiLCJzdWIiOiJNeVRlc3RVc2VyIiwianRpIjoiNWQyODU0MDkiLCJhdWQiOlsiaHR0cHM6Ly9sb2NhbGhvc3Q6NzE4OCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTA1NiJdLCJuYmYiOjE2NTQ2NDk2MzcsImV4cCI6MTY2MjU5ODQzNywiaWF0IjoxNjU0NjQ5NjM3LCJpc3MiOiJkb3RuZXQtdXNlci1qd3RzIn0.Lggk5aPZm0gRmMi180HNOt1_XDs6Fa4QsAHmaHaHPhc" http://localhost:5000/secret
Hello MyTestUser. This is a secret!
MyWebApi$ 最后,让我们创建一个带有自定义声明的 JWT,允许访问我们示例应用中最机密的 API:
MyWebApi$ dotnet user-jwts create --name AnotherUser --scope "myapi:secrets"
JWT for user 'AnotherUser' created with id 'e9b480cb'.
MyWebApi$ dotnet user-jwts print e9b480cb
Found JWT with ID 'e9b480cb'
{
"Id": "e9b480cb",
"Scheme": "Bearer",
"Name": "AnotherUser",
"Audience": "https://localhost:7188",
"NotBefore": "2022-06-08T00:57:43+00:00",
"Expires": "2022-09-08T00:57:43+00:00",
"Issued": "2022-06-08T00:57:43+00:00",
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE",
"Scopes": [
"myapi:secrets"
],
"Roles": [],
"CustomClaims": {}
}
Token Header: {"alg":"HS256","typ":"JWT"}
Token Payload: {"unique_name":"AnotherUser","sub":"AnotherUser","jti":"e9b480cb","scope":"myapi:secrets","aud":["https://localhost:7188","http://localhost:5056"],"nbf":1654649863,"exp":1662598663,"iat":1654649863,"iss":"dotnet-user-jwts"}
Compact Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE
MyWebApi$ curl -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkFub3RoZXJVc2VyIiwic3ViIjoiQW5vdGhlclVzZXIiLCJqdGkiOiJlOWI0ODBjYiIsInNjb3BlIjoibXlhcGk6c2VjcmV0cyIsImF1ZCI6WyJodHRwczovL2xvY2FsaG9zdDo3MTg4IiwiaHR0cDovL2xvY2FsaG9zdDo1MDU2Il0sIm5iZiI6MTY1NDY0OTg2MywiZXhwIjoxNjYyNTk4NjYzLCJpYXQiOjE2NTQ2NDk4NjMsImlzcyI6ImRvdG5ldC11c2VyLWp3dHMifQ.U88zp4my_Po0WMsZ1irVFraKTJWHIsOy8MiQ2TkmteE" http://localhost:5000/special-secret
This is a special secret!
MyWebApi$ 使用新工具,我们能够验证示例应用程序中的 API 是否按照我们预期的方式进行了授权配置。该应用程序现在当然可以根据提供者的要求配置为支持实际的 JWT 提供者。user-jwts
您可以使用该选项探索可用的命令和选项,例如:user-jwts--help
此预览版扩展了最小 API 的参数绑定,以支持将最小 API 重构为一个接受一组参数的 API,该 API 接受具有表示曾经参数的顶级属性的单个对象。
例如,以下 API 列出了给定类别中的所有产品:
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Requires the Microsoft.EntityFrameworkCore.InMemory package
builder.Services.AddDbContext(options => options.UseInMemoryDatabase("products"));
var app = builder.Build();
app.MapGet("/categories/{categoryId}/products", (int categoryId, int pageSize, int page, ILogger logger, MyDb db) =>
{
logger.LogInformation("Getting products for page {Page}", page);
return db.Products.Where(p => p.CategoryId == categoryId).Skip((page - 1) * pageSize).Take(pageSize);
});
app.Run();
record Product (int Id, string Name, int CategoryId);
class MyDb : DbContext
{
public MyDb(DbContextOptions options) : base(options) { }
public DbSet Products { get; set; }
} 您现在可以将您的 API 参数重构为一个类型并将新属性添加AsParameters到您的参数中,如下所示:
app.MapGet("/categories/{categoryId}/products", ([AsParameters] ProductRequest req) =>
{
req.Logger.LogInformation("Getting products for page {Page}", req.Page);
return req.Db.Products.Where(p => p.CategoryId == req.CategoryId).Skip((req.Page - 1) * req.PageSize).Take(req.PageSize);
});
record struct ProductRequest(
int CategoryId,
int PageSize,
int Page,
ILogger Logger,
MyDb Db); 参数绑定规则将应用于新类型的顶级属性或参数化构造函数参数。此外,支持相同的绑定属性(FromRoute、FromQuery、FromServices等)并可应用于它们。
让我们更新前面的示例以绑定请求标头Page而PageSize不是查询字符串:
// You will need to include 'using Microsoft.AspNetCore.Mvc;'
record struct ProductRequest(
int CategoryId,
[FromHeader(Name = "PageSize")] int PageSize,
[FromHeader(Name = "Page")] int Page,
ILogger Logger,
MyDb Db); classes和均受structs支持(structs建议使用 以避免额外的内存分配)。但是,不支持abstract类型和。interfaces在我们之前的示例中,相同的类型(当前为 a )可以定义为 a :record structclass
class ProductRequest
{
public int CategoryId { get; set; }
[FromHeader(Name = "PageSize")]
public int PageSize { get; set; }
[FromHeader(Name = "Page")]
public int Page { get; set; }
public ILogger Logger { get; set; }
public MyDb Db { get; set; }
} 在参数绑定期间应用以下规则:
类
结构
注意:使用无参数构造函数绑定时,所有公共可设置属性都将被绑定。
| 留言与评论(共有 0 条评论) “” |