C#/.NET6:从访问令牌 (OAuth/OpenID) 中的范围为 API 控制器构建策略

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

我有一个 API 控制器,我希望使用不记名令牌对消费服务进行授权。 API 端点应确保该服务在其令牌中具有所需的范围 - 但这对我不起作用。

我有一个由某个机构颁发的访问令牌。它的有效负载看起来像这样(显然是虚拟值):

{
  "iss": "https://someauthority.com",
  "nbf": 1699891816,
  "iat": 1699891816,
  "exp": 1699895416,
  "aud": "https://myapi.com",
  "scope": [
    "myapi:user-read"
  ],
  "client_id": "MyApiConsumer",
  "tenant_id": "fcbebe85-5e17-4986-dffd-ede94e9b6a07",
  "tenant_external_id": "7123",
  "tenant_owner_client_id": "SomeTenantOwnerApp",
  "jti": "ADE83169F38F3EA14B5E99AF998821EF"
}

首先,我使用

Microsoft.AspNetCore.Authentication.JwtBearer
库验证令牌:

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
    .AddJwtBearer(options =>
    {
        options.Authority = "https://someauthority.com";
        options.Audience = "https://myapi.com";
        options.SaveToken = true; // Tried both, with and without
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero,
            IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
            {
                var json = new WebClient().DownloadString("https://myapi.com/.well-known/openid-configuration/jwks");
                var keys = JsonConvert.DeserializeObject<JwksKeys>(json);
                return keys?.Keys;
            }
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("AuthenticationFailed");
                logger.LogError("Token validation failed", context.Exception);
                return Task.CompletedTask;
            },
            OnTokenValidated = context =>
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("TokenValidated");
                logger.LogInformation("Token validated successfully.");
                logger.LogInformation("Claims:");
                foreach (var claim in context.Principal.Claims)
                {
                    logger.LogInformation($"{claim.Type}: {claim.Value}");
                }
                return Task.CompletedTask;
            }
        };
    });

在我添加的日志输出中,我可以清楚地看到范围声明就在那里。此外,令牌验证也有效。所以我想我可以继续构建一些可以在控制器中用作装饰器的策略,如下所示:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ScopeUserRead", policy => policy.RequireClaim("scope", "myapi:user-read"));
    options.AddPolicy("ScopeUserCreate", policy => policy.RequireClaim("scope", "myapi:user-create"));
    options.AddPolicy("ScopeUserWrite", policy => policy.RequireClaim("scope", "myapi:user-read-write"));
});

在我的控制器中,在类上使用

[Authorize]
装饰器,在方法上使用
[Authorize("ScopeUserRead")]

不过,使用上面的令牌调用 API 确实会产生 401 错误。

我了解到,我用来构建策略的

RequireClaim
方法从
HttpContext
的 User 对象获取范围声明 - 该对象为空,因为这是访问令牌,而不是 ID 令牌。然而,一些消息来源表示,在验证过程中,应将声明从令牌中的主体对象复制到
HttpContext
用户对象。当我像这样检查 User 对象时,我发现这在我的情况下是不正确的:

app.Use(async (context, next) =>
{
    var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("ClaimsMiddleware");
    var user = context.User;

    if (user.Identity.IsAuthenticated)
    {
        logger.LogInformation("Authenticated User Claims:");
        foreach (var claim in user.Claims)
        {
            logger.LogInformation($"{claim.Type}: {claim.Value}");
        }
    }
    else
    {
        logger.LogInformation("User is not authenticated.");
    }
    await next.Invoke();
});

所以我想知道我做错了什么,如果我的整个方法只是垃圾,我应该使用不同的库还是应该尝试将声明手动复制到 User 对象(如果可能的话)?

此外,这应该是一个多租户 API,因此控制器方法需要知道

tenant_id
声明的值。有什么方法可以使
tenant_id
作为我的控制器方法的参数,也许通过使用一些装饰器
[FromToken]
或者这只是我的梦想?或者我必须像
一样从
HttpContext

检索它吗?
var tenantId = HttpContext.User.FindFirst("tenant_id").Value;

在这种情况下,它需要出现在

User
对象中,但它不是......

c# .net oauth jwt openid
1个回答
0
投票

根据您的描述,您没有在正确的时间进行身份验证并收到 401 错误, 确保您的中间件顺序正确:

app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
© www.soinside.com 2019 - 2024. All rights reserved.