提问人:Aileron79 提问时间:11/14/2023 最后编辑:Ruikai FengAileron79 更新时间:11/14/2023 访问量:44
C#/.NET6:从访问令牌 (OAuth/OpenID) 中的范围为 API 控制器构建策略
C#/.NET6: Build Policies for an API Controller from Scopes in an Access Token (OAuth/OpenID)
问:
我有一个 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 错误。
我了解到,我用于构建策略的方法从 - 的 User 对象中获取范围声明,该对象为空,因为这是访问令牌,而不是 ID 令牌。但是,一些消息来源说,在验证期间,应将声明从令牌中的 Principal 对象复制到 User 对象。我发现这在我的情况下是不正确的,因为我像这样检查了 User 对象:RequireClaim
HttpContext
HttpContext
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();
});
所以我想知道我做错了什么,如果我的整个方法只是垃圾,我应该使用不同的库还是应该尝试手动将声明复制到用户对象(如果可能的话)?
此外,这应该是一个多租户 API,因此控制器方法需要知道声明的值。有没有办法让它作为我的控制器方法的参数可用,也许是通过使用一些装饰器,或者这只是我的梦想?或者我必须从喜欢中检索它tenant_id
tenant_id
[FromToken]
HttpContext
var tenantId = HttpContext.User.FindFirst("tenant_id").Value;
在这种情况下,它需要存在于对象中,而它不是......User
答:
从您的描述来看,您没有在正确的时间进行身份验证并收到 401 错误, 确保您的中间件顺序正确:
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
评论