项目
版本

迁移到 OpenIddict 4.0

有哪些新变化?

4.0 版本中最重要的变化可以在 这里 找到。

除非你正在使用 MongoDB,迁移到 OpenIddict 4.0 不需要对数据库做出更改

更新你的包引用

为此,请更新你的 .csproj 文件以引用 OpenIddict 4.x 版本的包。例如:

<ItemGroup>
  <!-- OpenIddict 3.x: -->
  <PackageReference Include="OpenIddict.AspNetCore" Version="3.1.1" />
  <PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.1.1" />

  <!-- OpenIddict 4.x: -->
  <PackageReference Include="OpenIddict.AspNetCore" Version="4.10.1" />
  <PackageReference Include="OpenIddict.EntityFrameworkCore" Version="4.10.1" />
</ItemGroup>

迁移到 ASP.NET Core 7.0 并非必要,因为 OpenIddict 4.0 依然原生兼容 ASP.NET Core 2.1(仅限 .NET Framework)、ASP.NET Core 3.1 和 ASP.NET Core 6.0。迁移到更新的 .NET 运行时或 ASP.NET Core 可以单独进行,以便进行更简单/解耦的升级:

Web 框架版本 .NET 运行时版本
ASP.NET Core 2.1 .NET Framework 4.6.1
ASP.NET Core 2.1 .NET Framework 4.7.2
ASP.NET Core 2.1 .NET Framework 4.8
ASP.NET Core 3.1 .NET Core 3.1
ASP.NET Core 6.0 .NET 6.0
ASP.NET Core 7.0 .NET 7.0
Microsoft.Owin 4.2 .NET Framework 4.6.1
Microsoft.Owin 4.2 .NET Framework 4.7.2
Microsoft.Owin 4.2 .NET Framework 4.8

更新你的端点 URI

OpenIddict 4.0 引入了一个行为变化,影响端点 URI 的计算和解析方式。关于这一变化的更多信息,请阅读 OpenIddict 4.0 中影响 URI 处理的破坏性变化

在大多数情况下,调整你的代码应仅限于删除端点路径中的前导斜杠,以适应新的逻辑:

services.AddOpenIddict()
    .AddServer(options =>
    {
        // OpenIddict 3.x:
        options.SetAuthorizationEndpointUris("/connect/authorize")
               .SetDeviceEndpointUris("/connect/device")
               .SetIntrospectionEndpointUris("/connect/introspect")
               .SetLogoutEndpointUris("/connect/logout")
               .SetTokenEndpointUris("/connect/token")
               .SetUserinfoEndpointUris("/connect/userinfo")
               .SetVerificationEndpointUris("/connect/verify");

        // OpenIddict 4.x:
        options.SetAuthorizationEndpointUris("connect/authorize")
               .SetDeviceEndpointUris("connect/device")
               .SetIntrospectionEndpointUris("connect/introspect")
               .SetLogoutEndpointUris("connect/logout")
               .SetTokenEndpointUris("connect/token")
               .SetUserinfoEndpointUris("connect/userinfo")
               .SetVerificationEndpointUris("connect/verify");
    });

移除指定目标列表的 AddClaim(s)调用:

正如 OpenIddict 4.0 预览版 1 发布 中解释的那样,接受 destinations 参数的 AddClaim(s) 扩展方法在 4.0 中已被移除。

相反,建议开发者使用针对 ClaimsIdentityClaimsPrincipal 的新一键式 SetDestinations() 扩展方法(该方法必须在所有声明被添加到身份/主体之后调用)。

var identity = new ClaimsIdentity(
    authenticationType: TokenValidationParameters.DefaultAuthenticationType,
    nameType: Claims.Name,
    roleType: Claims.Role);

identity.SetClaim(Claims.Subject, await _userManager.GetUserIdAsync(user))
        .SetClaim(Claims.Email, await _userManager.GetEmailAsync(user))
        .SetClaim(Claims.Name, await _userManager.GetUserNameAsync(user))
        .SetClaims(Claims.Role, (await _userManager.GetRolesAsync(user)).ToImmutableArray());

identity.SetScopes(result.Principal.GetScopes());
identity.SetResources(await _scopeManager.ListResourcesAsync(identity.GetScopes()).ToListAsync());

identity.SetDestinations(static claim => claim.Type switch
{
    // 允许“name”声明在访问令牌和身份令牌中存储,当授予了 “profile” 作用域(通过调用principal.SetScopes(...)实现
    Claims.Name when claim.Subject.HasScope(Scopes.Profile)
        => [Destinations.AccessToken, Destinations.IdentityToken],

    // 如果不满足上述条件,仅在访问令牌中存储该声明
    _ => [Destinations.AccessToken]
});

如适用,请更新你的 OpenIddict MongoDB 授权

为了与其他属性使用的大小写相匹配,OpenIddictMongoDbAuthorization.CreationDate 属性在 BSON 表示中的名称已修复为使用驼峰命名法(即 creation_date 而不是 CreationDate)。为了确保现有授权正确更新以使用新名称,可以使用以下脚本非常高效地一次性更新所有现有授权:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using OpenIddict.MongoDb;

var services = new ServiceCollection();
services.AddOpenIddict()
    .AddCore()
    .UseMongoDb()
    .UseDatabase(new MongoClient("mongodb://localhost:27017").GetDatabase("openiddict"));

await using var provider = services.BuildServiceProvider();
var context = provider.GetRequiredService<IOpenIddictMongoDbContext>();
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictMongoDbOptions>>().CurrentValue;
var database = await context.GetDatabaseAsync(CancellationToken.None);

var authorizations = database.GetCollection<BsonDocument>(options.AuthorizationsCollectionName);
await authorizations.UpdateManyAsync(
    filter: Builders<BsonDocument>.Filter.Empty,
    update: Builders<BsonDocument>.Update.Rename("CreationDate", "creation_date"));

如适用,请将对 Portable.BouncyCastle 的引用替换为 BouncyCastle.Cryptography

虽然 OpenIddict 的早期版本使用了由 Claire Novotny 维护的非官方 Portable.BouncyCastle 包(这在当时是最佳的.NET Standard 兼容选项),但 OpenIddict 4.0 已更新为使用官方包 BouncyCastle.Cryptography ,该包于 2022 年 11 月发布,完全支持.NET Standard 2.0。 如果您的应用程序使用了 Portable.BouncyCastle,强烈建议迁移到 BouncyCastle.Cryptography 以避免类型冲突。

如适用,请更新自定义存储以使用更新后的签名

OpenIddict 4.x 修正了 IOpenIddictApplicationStore.GetAsync()IOpenIddictAuthorizationStore.GetAsync()IOpenIddictScopeStore.GetAsync()IOpenIddictTokenStore.GetAsync() 的可空性注解,使其返回 ValueTask<T> 而非 ValueTask<T>?

实现了这些接口并且启用了可空引用的开发者应被邀请更新 GetAsync() 方法的签名,以匹配这些更改:

// OpenIddict 3.x:
ValueTask<TResult> GetAsync<TState, TResult>(
    Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
    TState state, CancellationToken cancellationToken);

// OpenIddict 4.x:
ValueTask<TResult?> GetAsync<TState, TResult>(
    Func<IQueryable<TApplication>, TState, IQueryable<TResult>> query,
    TState state, CancellationToken cancellationToken);

尽管不是必需的,但也建议更新 IOpenIddictApplicationStore 的实现,以使用针对 FindByPostLogoutRedirectUriAsync()FindByRedirectUriAsync()SetPostLogoutRedirectUrisAsync()SetRedirectUrisAsync() 更新后的参数名称:

// OpenIddict 3.x:
IAsyncEnumerable<TApplication> FindByPostLogoutRedirectUriAsync(string address, CancellationToken cancellationToken);
IAsyncEnumerable<TApplication> FindByRedirectUriAsync(string address, CancellationToken cancellationToken);
ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, ImmutableArray<string> addresses, CancellationToken cancellationToken);
ValueTask SetRedirectUrisAsync(TApplication application, ImmutableArray<string> addresses, CancellationToken cancellationToken);

// OpenIddict 4.x:
IAsyncEnumerable<TApplication> FindByPostLogoutRedirectUriAsync(string uri, CancellationToken cancellationToken);
IAsyncEnumerable<TApplication> FindByRedirectUriAsync(string uri, CancellationToken cancellationToken);
ValueTask SetPostLogoutRedirectUrisAsync(TApplication application, ImmutableArray<string> uris, CancellationToken cancellationToken);
ValueTask SetRedirectUrisAsync(TApplication application, ImmutableArray<string> uris, CancellationToken cancellationToken);

考虑迁移到新的 OpenIddict 客户端(可选)

OpenIddict 4.0 引入了一个新的客户端堆栈,它原生兼容所有受支持的 ASP.NET Core 版本(.NET Framework 上的 2.1、3.1、6.0 和 7.0)以及Microsoft.Owin 4.2(这意味着它也可以在 ASP.NET 4.6.1 及以上版本上使用)。

欲了解更多详情,请阅读 OpenIddict 4.0 正式可用

在本文档中