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

3lxsmp7m  于 2023-11-20  发布在  .NET
关注(0)|答案(1)|浏览(128)

我有一个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;
            }
        };
    });


在我添加的日志输出中,我可以清楚地看到scope声明在那里。而且,token验证工作。所以我想我可以继续构建一些策略,我可以在我的控制器中使用它们作为装饰器,就像这样:

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令牌。有几个来源说,在验证期间,声明应该从令牌中的Principal对象复制到HttpContext User对象。我这样检查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对象中,而不是...

iibxawm4

iibxawm41#

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

app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();

字符串

相关问题