OpenIddict,重新启动身份验证服务器后令牌将失效

And*_*riy 3 oauth jwt openid-connect openiddict

我进行了以下设置:授权服务器(带有 MVC 的 .NET 6,端口 7000)、客户端(带有 MVC 的 .NET 6,端口 7001)、资源服务器(.NET 6 API,端口 7002)。

授权服务器设置:

builder.Services.AddAuthentication()
   .AddGoogle(options =>
   {
       options.ClientId = builder.Configuration["ClientId"];
       options.ClientSecret = builder.Configuration["ClientSecret"];
   });

builder.Services.Configure<IdentityOptions>(options =>
{
    options.ClaimsIdentity.UserNameClaimType = Claims.Name;
    options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
    options.ClaimsIdentity.RoleClaimType = Claims.Role;
    options.ClaimsIdentity.EmailClaimType = Claims.Email;

    options.SignIn.RequireConfirmedAccount = false;
});

builder.Services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore()
                .UseDbContext<AuthorizationContext>();
    })
    .AddServer(options =>
    {
        options.SetAuthorizationEndpointUris("/connect/authorize")
                .SetLogoutEndpointUris("/connect/logout")
                .SetTokenEndpointUris("/connect/token")
                .SetUserinfoEndpointUris("/connect/userinfo")
                .SetIntrospectionEndpointUris("/connect/introspect");

        options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);

        options.AllowAuthorizationCodeFlow();

        options.AddDevelopmentEncryptionCertificate()
                .AddDevelopmentSigningCertificate();

        options.UseAspNetCore()
                .EnableAuthorizationEndpointPassthrough()
                .EnableLogoutEndpointPassthrough()
                .EnableTokenEndpointPassthrough()
                .EnableUserinfoEndpointPassthrough()
                .EnableStatusCodePagesIntegration();
    })
    .AddValidation(options =>
    {
        options.UseLocalServer();

        options.UseAspNetCore();
    });

builder.Services.AddHostedService<Worker>();
Run Code Online (Sandbox Code Playgroud)

种子客户:

            await manager.CreateAsync(new OpenIddictApplicationDescriptor
            {
                ClientId = "mvc",
                ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654",
                ConsentType = ConsentTypes.Explicit,
                DisplayName = "MVC client application",
                PostLogoutRedirectUris =
                {
                    new Uri("https://localhost:7001/signout-callback-oidc")
                },
                    RedirectUris =
                {
                    new Uri("https://localhost:7001/signin-oidc")
                },
                    Permissions =
                {
                    Permissions.Endpoints.Authorization,
                    Permissions.Endpoints.Logout,
                    Permissions.Endpoints.Token,
                    Permissions.GrantTypes.AuthorizationCode,
                    Permissions.GrantTypes.RefreshToken,
                    Permissions.ResponseTypes.Code,
                    Permissions.Scopes.Email,
                    Permissions.Scopes.Profile,
                    Permissions.Scopes.Roles,
                    Permissions.Prefixes.Scope + "api1"
                },
                    Requirements =
                {
                    Requirements.Features.ProofKeyForCodeExchange
                }
            });

            // resource server
            if (await manager.FindByClientIdAsync("resource_server_1") == null)
            {
                var descriptor = new OpenIddictApplicationDescriptor
                {
                    ClientId = "resource_server_1",
                    ClientSecret = "846B62D0-DEF9-4215-A99D-86E6B8DAB342",
                    Permissions =
                        {
                            Permissions.Endpoints.Introspection
                        }
                };

                await manager.CreateAsync(descriptor);
            }
Run Code Online (Sandbox Code Playgroud)

客户端配置:

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
    options.LoginPath = "/login";
    options.ExpireTimeSpan = TimeSpan.FromMinutes(50);
    options.SlidingExpiration = false;
})
.AddOpenIdConnect(options =>
{
    options.ClientId = "mvc";
    options.ClientSecret = "901564A5-E7FE-42CB-B10D-61EF6A8F3654";

    options.RequireHttpsMetadata = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;

    options.ResponseType = OpenIdConnectResponseType.Code;
    options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;

    options.Authority = "https://localhost:7000/";

    options.Scope.Add("email");
    options.Scope.Add("roles");
    options.Scope.Add("api1");

    options.MapInboundClaims = false;

    options.TokenValidationParameters.NameClaimType = "name";
    options.TokenValidationParameters.RoleClaimType = "role";
});
Run Code Online (Sandbox Code Playgroud)

资源服务器配置:

builder.Services.AddOpenIddict()
    .AddValidation(options =>
    {
        options.SetIssuer("https://localhost:7000/");
        options.AddAudiences("resource_server_1");

        options.UseIntrospection()
               .SetClientId("resource_server_1")
               .SetClientSecret("846B62D0-DEF9-4215-A99D-86E6B8DAB342");

        options.UseSystemNetHttp();

        options.UseAspNetCore();
    });

builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
Run Code Online (Sandbox Code Playgroud)

这是客户端向资源服务器发出请求的方式:

    [Authorize, HttpPost("~/")]
    public async Task<ActionResult> Index(CancellationToken cancellationToken)
    {
        var token = await HttpContext.GetTokenAsync(CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectParameterNames.AccessToken);
        if (string.IsNullOrEmpty(token))
        {
            throw new InvalidOperationException("The access token cannot be found in the authentication ticket. " +
                                                "Make sure that SaveTokens is set to true in the OIDC options.");
        }

        using var client = _httpClientFactory.CreateClient();

        using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:7002/api/message");
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);

        using var response = await client.SendAsync(request, cancellationToken);
        var content = await response.Content.ReadAsStringAsync();
        response.EnsureSuccessStatusCode();

        return View("Home", model: await response.Content.ReadAsStringAsync());
    }
Run Code Online (Sandbox Code Playgroud)

当我设置这 3 个实例(身份验证服务器、客户端、资源服务器)并且我未在客户端中进行身份验证(没有 cookie)时,问题就出现了。我可以在客户端上进行身份验证(因此也可以在身份验证服务器上进行身份验证)。然后我从客户端向资源服务器发出请求并返回200

但随后我停止了所有 3 个实例并尝试再次执行此操作。

那时我已经在客户端(cookie)中进行了身份验证,并且可以提取令牌(仅供参考,停止实例之前和之后的请求之间的令牌是相同的)。但该令牌无效,资源服务器的响应代码为401

在资源服务器日志上,我可以看到以下日志:“ OpenIddict.Validation.AspNetCore was not authenticated. Failure message: An error occurred while authenticating the current request”和“ invalid_token, the specified token is invalid

问题:这是预期的行为吗?我认为原因是数据保护更改了密钥环或类似的东西。如果是预期的 - 那么如何在不重新验证所有用户的情况下进行重新部署?

小智 5

我很确定问题出在这条线上

    options.AddDevelopmentEncryptionCertificate()
            .AddDevelopmentSigningCertificate();
Run Code Online (Sandbox Code Playgroud)

当您重新启动应用程序时,这些证书(密钥)将会更改。您需要准备好生产加密/签名密钥。

请参阅 options.AddEncryptionKey 和 options.AddSigningKey

密钥可以这样创建

var rsa = RSA.Create(2048);
var key = new RsaSecurityKey(rsa);
Run Code Online (Sandbox Code Playgroud)

您可以获取密钥的 XML 并将其保存在私有位置

var xml = key.Rsa.ToXmlString(true);
Run Code Online (Sandbox Code Playgroud)

当您启动应用程序时,您可以使用 XML 加载密钥

var rsa = RSA.Create();
rsa.FromXmlString(xml);
Run Code Online (Sandbox Code Playgroud)

然后将密钥添加到 openiddict

options.AddEncryptionKey(rsa);
options.AddSigningKey(rsa);
Run Code Online (Sandbox Code Playgroud)

您可能还想使用以下方法

  • 选项.AddEncryptionCredentials
  • 选项.AddSigningCredentials
  • options.AddEncryptionCertificate
  • options.AddSigningCertificate

这取决于你有什么可用的。