InvalidOperationException:为方案“bearer”注册的身份验证处理程序是“JwtBearerHandler”,不能用于 SignInAsync

问题描述 投票:0回答:1

我的任务是做JWT认证。我的应用程序使用 .NET 6 和 ASP NET MVC。 我使用 OpenID Connect 在服务器端身份验证期间获取 JWT 访问令牌,然后将其自动添加到您授权的 API 调用的请求标头中。

我的代码在

Program.cs

const string AUTH_COOKIE_NAME = "access_token";
const string AUTH_SCHEMA_NAME = "OPENIDC_JWT";
const string BEARER_AUTH_SCHEMA_NAME = "bearer";
const string OIDC_AUTH_SCHEMA_NAME = "oidc";

builder.Services.AddAuthentication(options => {

    options.DefaultScheme = AUTH_SCHEMA_NAME;
    options.DefaultAuthenticateScheme = OIDC_AUTH_SCHEMA_NAME;
    options.DefaultChallengeScheme = OIDC_AUTH_SCHEMA_NAME;
})
    .AddOpenIdConnect(OIDC_AUTH_SCHEMA_NAME, o =>
    {
        o.MetadataAddress = builder.Configuration["SingleSignOn:MetaData"];
        o.Authority = builder.Configuration["SingleSignOn:Authority"];
        o.ClientId = builder.Configuration["SingleSignOn:ClientId"];
        o.ClientSecret = builder.Configuration["SingleSignOn:ClientSecret"];
        o.ResponseType = OpenIdConnectResponseType.Code;
        o.SaveTokens = true;
        o.Scope.Clear();
        o.Scope.Add("openid");
        o.Scope.Add("email");
        o.CallbackPath = "/signin-oidc";
        o.AccessDeniedPath = "/Account/AccessDenied";
        o.SignInScheme = BEARER_AUTH_SCHEMA_NAME;
        
        o.Events = new OpenIdConnectEvents
        {
            OnTokenValidated = async context =>
            {
                var accessToken = context.SecurityToken as JwtSecurityToken;
                if (accessToken != null)
                {
                    // Store the access token in the authentication properties
                    context.Properties.StoreTokens(new[]
                    {
                        new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = accessToken.RawData },
                    });
                }
               
                context.Request.Headers.Append("Authorization", $"Bearer {accessToken.RawData}");
                var roleService = context.HttpContext.RequestServices.GetRequiredService<IUserRolesService>();
                roleService.ManageUserRoles(context);
            },
        };
    })
    .AddJwtBearer(BEARER_AUTH_SCHEMA_NAME, options =>
    {
        options.MetadataAddress = "https://access-staging.epam.com/auth/realms/plusx/.well-known/openid-configuration";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true, // You shoud set ValidateAudience = true and specify ValidAudience for your application.
            ValidAudience = builder.Configuration["SingleSignOn:ClientId"],
            ValidateIssuerSigningKey = true,
        };
        options.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                string authCookie = context.Request.Cookies[AUTH_COOKIE_NAME];

                // Token will be taken from Authorization header or if header is not set from authentication cookie.
                if (!string.IsNullOrEmpty(authCookie))
                {
                    context.Token = authCookie;
                }
                return Task.CompletedTask;
            }
        };
    })
    .AddPolicyScheme(AUTH_SCHEMA_NAME, AUTH_SCHEMA_NAME, options =>
    {
        options.ForwardDefaultSelector = context =>
        {
            string authHeader = context.Request.Headers[HeaderNames.Authorization];
            string authCookie = context.Request.Cookies[AUTH_COOKIE_NAME];

            // If Token presents in header or cookie choose "bearer" schema, in other case use "oidc" schema.
            if (!string.IsNullOrEmpty(authHeader) || !string.IsNullOrEmpty(authCookie))
                return BEARER_AUTH_SCHEMA_NAME;

            return OIDC_AUTH_SCHEMA_NAME;
        };
    });

builder.Services.AddAuthorization();

// more services

var app = builder.Build();

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();//HTTP Strict Transport Security Protocol is used to be sure that none of your content is still server over HTTP
}

app.UseHttpsRedirection();  
app.UseStaticFiles();
app.UseMiddleware<SecurityHeadersMiddleware>();
app.UseRouting();
app.UseAuthentication();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Admin}/{action=Index}/{id?}");

app.Run();

正如您从代码中看到的,我通过 OpenIdConnect 向我的公司进行身份验证,接收 JWT 访问令牌并将其插入标头中,以便通过此 JWT 令牌与我公司的其他服务进一步通信。当客户端需要调用受保护的 API 端点时,它必须使用

Authorization
方案在
Bearer
标头中包含 JWT 令牌,如下所示:

Authorization: Bearer your_jwt_token

错误跟踪:上下文进入服务,根据用户的电子邮件,为他们分配声明。服务示例:

public void ManageUserRoles(TokenValidatedContext context)
{
    var user = context.Principal;

    if (user.Identity.IsAuthenticated)
    {
        if (!user.HasClaim(c => c.Type == ClaimTypes.Role))
        {
          if (!string.IsNullOrEmpty(userEmail) && isAdmin)
           {
             _logger.LogInformation($"Set the '{RoleNames.Admin}' role to user with email:  '{userEmail}'");
              identity.AddClaim(new Claim(ClaimTypes.Role, RoleNames.Admin));
            }

我的错误发生在我离开事件“OnTokenValidated”后:

InvalidOperationException: The authentication handler registered for scheme 'bearer' is 'JwtBearerHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: OPENIDC_JWT.

我正在使用

AddPolicyScheme
之类的处理程序,以防万一我在标头中有 JWT 令牌,我正在使用
JWTBearer
身份验证,验证令牌并进入本地主机上的应用程序;否则,如果标头中的 JWT 令牌为空,我必须通过我公司的身份提供商进行身份验证,接收令牌,将其添加到标头,然后在“AddJwtBearer”中验证它并授权到我的应用程序中。

还要提一下,我的所有控制器上都有属性

[Authorize]
。并且不要让我在身份验证服务中使用
.AddCookie
或使用基于 cookie 的身份验证方案。

我尝试仅使用承载身份验证方案作为默认值:

builder.Services.AddAuthentication(options => {

    options.DefaultScheme = BEARER_AUTH_SCHEMA_NAME;
   
})

-- 之后什么也没有发生。

我尝试在身份验证方案中使用不同的方案变体:

options.DefaultScheme = AUTH_SCHEMA_NAME;
options.DefaultAuthenticateScheme = AUTH_SCHEMA_NAME;
options.DefaultChallengeScheme = AUTH_SCHEMA_NAME;

-- 之后同样的错误:

InvalidOperationException: The authentication handler registered for scheme 'bearer' is 'JwtBearerHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: OPENIDC_JWT.

尝试仅使用一种默认方案:

    options.DefaultScheme = AUTH_SCHEMA_NAME;

之后同样的错误:

InvalidOperationException: The authentication handler registered for scheme 'bearer' is 'JwtBearerHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: OPENIDC_JWT.

也尝试过这个变体:

builder.Services.AddAuthentication(options => {

    options.DefaultScheme = BEARER_AUTH_SCHEMA_NAME;
    options.DefaultChallengeScheme = OIDC_AUTH_SCHEMA_NAME;
   
})

之后同样的错误:

InvalidOperationException: The authentication handler registered for scheme 'bearer' is 'JwtBearerHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: OPENIDC_JWT.

堆栈跟踪:

`System.InvalidOperationException: The authentication handler registered for scheme 'bearer' is 'JwtBearerHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: OPENIDC_JWT.

   at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)

   at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()

   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)

   at Middlewares.SecurityHeadersMiddleware.InvokeAsync(HttpContext context) in Middlewares\SecurityHeadersMiddleware.cs:line 23

   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)`

更新: 我的工作示例如下。通过设置

AddAuthentication
选项并指定身份验证方案解决了身份验证方案的问题。如果您需要对您的身份进行一些自定义声明,您需要实现 IClaimsTransformation 接口以在身份验证过程中具有内置声明逻辑。

 // Add services to the container.
builder.Services.AddTransient<IManageUsersRolesJwtContext, UserRolesService>();
builder.Services.AddTransient<IClaimsTransformation, ShipmentClaimsTransformation>();

// Define constants for authentication schemes and cookie name const string
const string AUTH_COOKIE_NAME = "access_token";
const string CUSTOM_POLICY_SCHEMA_NAME = "OIDC_JWT";
const string BEARER_AUTH_SCHEMA_NAME = "Bearer";
const string OIDC_AUTH_SCHEMA_NAME = "oidc";
const string METADATA_ADDRESS = "https://***/openid-configuration";

// Configure the cookie policy options
builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
});

// Configure the authentication services 
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CUSTOM_POLICY_SCHEMA_NAME;
    options.DefaultAuthenticateScheme = BEARER_AUTH_SCHEMA_NAME;
    options.DefaultChallengeScheme = OIDC_AUTH_SCHEMA_NAME;
})
    // Add custom policy scheme for handling bearer and OIDC authentication
    .AddOpenIdConnect(OIDC_AUTH_SCHEMA_NAME, o =>
    {
        o.MetadataAddress = builder.Configuration["SingleSignOn:MetaData"];
        o.Authority = builder.Configuration["SingleSignOn:Authority"];
        o.ClientId = builder.Configuration["SingleSignOn:ClientId"];
        o.ClientSecret = builder.Configuration["SingleSignOn:ClientSecret"];
        o.GetClaimsFromUserInfoEndpoint = true;
        o.ResponseType = "code";
        o.SaveTokens = true;
        o.Scope.Clear();
        o.Scope.Add("openid");
        o.Scope.Add("profile");
        o.Scope.Add("email");
        o.CallbackPath = "/signin-oidc";
        o.Events = new OpenIdConnectEvents
        {
            // Store access token in authentication properties when token is validated
            OnTokenValidated = context =>
            {
                var accessToken = context.SecurityToken as JwtSecurityToken;
                if (accessToken != null)
                {
                    // Store the access token in the authentication properties
                    context.Properties.StoreTokens(new[]
                    {
                        new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = accessToken.RawData },
                    });
                }

                // Set the authentication cookie
                context.HttpContext.Response.Cookies.Append(AUTH_COOKIE_NAME, accessToken.RawData, new CookieOptions
                {
                    HttpOnly = true,
                    Secure = true,
                    SameSite = SameSiteMode.None,
                    Expires = context.SecurityToken.ValidTo.AddMinutes(-1)
                });
                context.Response.Headers.Add("Authorization", $"Bearer {accessToken.RawData}");
                // Redirect and handle response
                context.Response.Redirect(context.Properties.RedirectUri);
                context.HandleResponse();
                return Task.CompletedTask;
            }
        };
    })
    // Add JWT Bearer authentication
    .AddJwtBearer(BEARER_AUTH_SCHEMA_NAME, options =>
    {
        // Configure JWT options
        options.MetadataAddress = METADATA_ADDRESS;
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = builder.Configuration["SingleSignOn:Authority"],
            ValidateAudience = true, // You shoud set ValidateAudience = true and specify ValidAudience for your application.
            ValidAudience = builder.Configuration["SingleSignOn:ClientId"],
            ValidateIssuerSigningKey = true,
        };
        options.Events = new JwtBearerEvents
        {
            // Get the token from either the authorization header or authentication cookie
            OnMessageReceived = context =>
            {
                string authCookie = context.Request.Cookies[AUTH_COOKIE_NAME];
                string authHeader = context.Request.Headers[HeaderNames.Authorization];

                // Token will be taken from Authorization header or if header is not set from authentication cookie.
                if (!string.IsNullOrEmpty(authCookie))
                {
                     context.Token = authCookie;
                }
                return Task.CompletedTask;
            }
        };
    }).AddPolicyScheme(CUSTOM_POLICY_SCHEMA_NAME, CUSTOM_POLICY_SCHEMA_NAME, options =>
    {
        options.ForwardDefaultSelector = context =>
        {
            var isAuthenticated = context.User?.Identity?.IsAuthenticated ?? false;

            string authHeader = context.Request.Headers[HeaderNames.Authorization];
            string authCookie = context.Request.Cookies[AUTH_COOKIE_NAME];

            // If Token presents in the header or cookie choose "bearer" schema, in other case use "oidc" schema.
            if (!string.IsNullOrEmpty(authHeader) || !string.IsNullOrEmpty(authCookie))
                return BEARER_AUTH_SCHEMA_NAME;
            else
                return OIDC_AUTH_SCHEMA_NAME;
        };
    });

builder.Services.AddAuthorization();

    ```
c# authentication jwt asp.net-core-mvc openid-connect
1个回答
0
投票

JwtBearer 处理程序不实现用户的登录或注销。要登录用户,您通常会直接对 cookie 处理程序执行此操作,或者让 OpenIDConnect 处理程序执行此操作。 JwtBearer 仅接受访问令牌并将其转换为 ClaimsPrincipal 用户,这是其唯一目的。

下图(取自我的培训课程)比较了 cookie 和 JwtBearer 处理程序:

© www.soinside.com 2019 - 2024. All rights reserved.