我有一个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
对象中,而不是...
1条答案
按热度按时间iibxawm41#
根据您的描述,您没有在正确的时间进行身份验证并获得401错误,请确保您的中间件处于正确的顺序:
字符串