You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级OpenIddict 4.0.0后启用令牌加密时HotChocolate GraphQL授权失效

OpenIddict v4.0.0迁移后RSA加密令牌无法解密导致声明丢失问题

我有一个ASP.NET Core项目,同时托管了基于OpenIddict的身份服务器和基于HotChocolate GraphQL的资源服务器。系统启用了*客户端凭证(Client-credentials)*流,采用RSA算法加密令牌。之前使用OpenIddict v3.1.1时一切正常,升级到v4.0.0后授权功能失效。禁用令牌加密时功能恢复正常,但启用加密后,调试发现声明(Claims)完全无法传递。由于业务和安全要求不能关闭加密,且OpenIddict 3.x到4.x的迁移指南未提及加密密钥相关变更,同时v3.1.1已停止Bug修复支持,必须解决此问题。


ASP.NET Core管道中的OpenIddict配置

public static void AddOpenIddict(this IServiceCollection services, IConfiguration configuration)
{
        var openIddictOptions = configuration.GetSection("OpenIddict").Get<OpenIddictOptions>();

        var encryptionKeyData = openIddictOptions.EncryptionKey.RSA;
        var signingKeyData = openIddictOptions.SigningKey.RSA;

        var encryptionKey = RSA.Create();
        var signingKey = RSA.Create();

        encryptionKey.ImportFromEncryptedPem(encryptionKeyData.ToCharArray(),
            openIddictOptions.EncryptionKey.Passphrase.ToCharArray());
        signingKey.ImportFromEncryptedPem(signingKeyData.ToCharArray(),
            openIddictOptions.SigningKey.Passphrase.ToCharArray());

        encryptionKey.ImportFromEncryptedPem(encryptionKeyData.ToCharArray(),
            openIddictOptions.EncryptionKey.Passphrase.ToCharArray());
        signingKey.ImportFromEncryptedPem(signingKeyData.ToCharArray(),
            openIddictOptions.SigningKey.Passphrase.ToCharArray());

        var sk = new RsaSecurityKey(signingKey);
        var ek = new RsaSecurityKey(encryptionKey);

        services.AddOpenIddict()
            .AddCore(options =>
            {
                options.UseEntityFrameworkCore()
                    .UseDbContext<AuthDbContext>()
                    .ReplaceDefaultEntities<Guid>();
            })
            .AddServer(options =>
            {
                // 迁移指南提示:更新端点URI

                options.SetCryptographyEndpointUris("oauth2/.well-known/jwks");
                options.SetConfigurationEndpointUris("oauth2/.well-known/openid-configuration");
                options.SetTokenEndpointUris("oauth2/connect/token");
                options.AllowClientCredentialsFlow();
                options.SetUserinfoEndpointUris("oauth2/connect/userinfo");
                options.SetIntrospectionEndpointUris("oauth2/connect/introspection");

                options.AddSigningKey(sk);
                options.AddEncryptionKey(ek);

                //options.DisableAccessTokenEncryption(); // 取消注释此行后功能正常

                options.UseAspNetCore(o =>
                {
                    // 注意:禁用是因为默认OpenIddict只接受HTTPS端点请求
                    o.DisableTransportSecurityRequirement();
                    o.EnableTokenEndpointPassthrough();
                });
            })
            .AddValidation(options =>
            {
                options.UseLocalServer();
                options.UseAspNetCore();
            });
    }

授权控制器的令牌获取方法

[HttpPost("~/oauth2/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);
        if (application == null)
        {
            throw new InvalidOperationException("无法在数据库中找到应用程序详情。");
        }

        // 创建包含声明的ClaimsIdentity,用于生成id_token、令牌或授权码
        var identity = new ClaimsIdentity(
            authenticationType: TokenValidationParameters.DefaultAuthenticationType,
            nameType: OpenIddictConstants.Claims.Name,
            roleType: OpenIddictConstants.Claims.Role);

        var clientId = await _applicationManager.GetClientIdAsync(application);
        var organizationId = await _applicationManager.GetDisplayNameAsync(application);

        // 迁移指南提示:移除指定目标列表的AddClaims调用

        identity.SetClaim(type: OpenIddictConstants.Claims.Subject, value: organizationId)
                .SetClaim(type: OpenIddictConstants.Claims.ClientId, value: clientId)
                .SetClaim(type: "organization_id", value: organizationId);

        identity.SetDestinations(static claim => claim.Type switch
        {
            _ => new[] { OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken }
        });

        return SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    }

    throw new NotImplementedException("指定的授权类型未实现。");
}

授权失效的资源控制器

public class Query
{
    private readonly IMapper _mapper;

    public Query(IMapper mapper)
    {
        _mapper = mapper;
    }

[HotChocolate.AspNetCore.Authorization.Authorize]
    public async Task<Organization> GetMe(ClaimsPrincipal claimsPrincipal,
        [Service] IDbContextFactory<DbContext> dbContextFactory,
        CancellationToken ct)
    {
        var organizationId = Ulid.Parse(claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier));

        ... // 省略后续代码
    }
  }
}

ASP.NET Core管道中的GraphQL配置

public static void AddGraphQL(this IServiceCollection services, IWebHostEnvironment webHostEnvironment)
{
        services.AddGraphQLServer()
            .AddAuthorization()
            .AddQueryType<Query>()
}

使用的包及版本

  • OpenIddict (4.0.0)
  • OpenIddict.AspNetCore (4.0.0)
  • OpenIddict.EntityFrameworkCore (4.0.0)
  • HotChocolate.AspNetCore (12.13.2)
  • HotChocolate.AspNetCore.Authorization (12.13.2)
  • HotChocolate.Diagnostics (12.13.2)

补充:日志捕获的异常信息

Microsoft.IdentityModel.Tokens.SecurityTokenDecryptionFailedException: IDX10603: 解密失败。尝试的密钥: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey, KeyId: '', InternalId: 'nu9-WIVbfvvJafm3g4th-uHPFDf8eoyJf-M1ByrotW8'.
'.
捕获的异常:
'System.ArgumentNullException: IDX10000: 参数 'ciphertext' 不能为 'null' 或空对象。 (参数 'ciphertext')
在 Microsoft.IdentityModel.Tokens.AuthenticatedEncryptionProvider.Decrypt(Byte[] ciphertext, Byte[] authenticatedData, Byte[] iv, Byte[] authenticationTag)
在 Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.DecryptToken(CryptoProviderFactory cryptoProviderFactory, SecurityKey key, String encAlg, Byte[] ciphertext, Byte[] headerAscii, Byte[] initializationVector, Byte[] authenticationTag)
在 Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.DecryptJwtToken(SecurityToken securityToken, TokenValidationParameters validationParameters, JwtTokenDecryptionParameters decryptionParameters


ASP.NET Core管道中的令牌验证参数配置

services.AddAuthentication()
        .AddJwtBearer(options =>
        {
            var openIddictOptions = Configuration.GetSection("OpenIddict").Get<OpenIddictOptions>();
            var encryptionKeyData = openIddictOptions.EncryptionKey.RSA;
            var signingKeyData = openIddictOptions.SigningKey.RSA;

            var encryptionKey = RSA.Create();
            var signingKey = RSA.Create();

            encryptionKey.ImportFromEncryptedPem(encryptionKeyData.ToCharArray(),
                openIddictOptions.EncryptionKey.Passphrase.ToCharArray());
            signingKey.ImportFromEncryptedPem(signingKeyData.ToCharArray(),
                openIddictOptions.SigningKey.Passphrase.ToCharArray());

            encryptionKey.ImportFromEncryptedPem(encryptionKeyData.ToCharArray(),
                openIddictOptions.EncryptionKey.Passphrase.ToCharArray());
            signingKey.ImportFromEncryptedPem(signingKeyData.ToCharArray(),
                openIddictOptions.SigningKey.Passphrase.ToCharArray());

            var sk = new RsaSecurityKey(signingKey);
            var ek = new RsaSecurityKey(encryptionKey);

            options.IncludeErrorDetails = true;
            options.RequireHttpsMetadata = false;

            options.TokenValidationParameters = new TokenValidationParameters
            {
                IssuerSigningKey = sk,
                TokenDecryptionKey = ek,
                RequireExpirationTime = false,
                ValidateLifetime = true,
                ValidateAudience = false,
                ValidateIssuer = false
            };
        });

内容的提问来源于stack exchange,提问作者Sk Shahnawaz-ul Haque

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 03:54:24