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

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

我有一个API控制器,我希望使用不记名令牌授权使用服务。API端点应确保此服务在其令牌中具有所需的范围-这对我不起作用。
我有一个由某个权威机构颁发的访问令牌。它的有效载荷看起来像这样(显然是虚拟值):

  1. {
  2. "iss": "https://someauthority.com",
  3. "nbf": 1699891816,
  4. "iat": 1699891816,
  5. "exp": 1699895416,
  6. "aud": "https://myapi.com",
  7. "scope": [
  8. "myapi:user-read"
  9. ],
  10. "client_id": "MyApiConsumer",
  11. "tenant_id": "fcbebe85-5e17-4986-dffd-ede94e9b6a07",
  12. "tenant_external_id": "7123",
  13. "tenant_owner_client_id": "SomeTenantOwnerApp",
  14. "jti": "ADE83169F38F3EA14B5E99AF998821EF"
  15. }

字符串
首先,我使用Microsoft.AspNetCore.Authentication.JwtBearer库验证令牌:

  1. builder.Services.AddAuthentication(options =>
  2. {
  3. options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
  4. options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
  5. })
  6. .AddJwtBearer(options =>
  7. {
  8. options.Authority = "https://someauthority.com";
  9. options.Audience = "https://myapi.com";
  10. options.SaveToken = true; // Tried both, with and without
  11. options.TokenValidationParameters = new TokenValidationParameters
  12. {
  13. ValidateIssuerSigningKey = true,
  14. ValidateIssuer = true,
  15. ValidateAudience = true,
  16. ValidateLifetime = true,
  17. ClockSkew = TimeSpan.Zero,
  18. IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
  19. {
  20. var json = new WebClient().DownloadString("https://myapi.com/.well-known/openid-configuration/jwks");
  21. var keys = JsonConvert.DeserializeObject<JwksKeys>(json);
  22. return keys?.Keys;
  23. }
  24. };
  25. options.Events = new JwtBearerEvents
  26. {
  27. OnAuthenticationFailed = context =>
  28. {
  29. var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("AuthenticationFailed");
  30. logger.LogError("Token validation failed", context.Exception);
  31. return Task.CompletedTask;
  32. },
  33. OnTokenValidated = context =>
  34. {
  35. var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("TokenValidated");
  36. logger.LogInformation("Token validated successfully.");
  37. logger.LogInformation("Claims:");
  38. foreach (var claim in context.Principal.Claims)
  39. {
  40. logger.LogInformation($"{claim.Type}: {claim.Value}");
  41. }
  42. return Task.CompletedTask;
  43. }
  44. };
  45. });


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

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


在我的控制器中,在类上使用[Authorize]装饰器,在方法上使用[Authorize("ScopeUserRead")]
不过,使用上面的令牌调用API确实会产生401错误。
我了解到,我用来构建策略的RequireClaim方法从HttpContext的User对象中获取了作用域声明-该声明为空,因为这是一个访问令牌,而不是ID令牌。有几个来源说,在验证期间,声明应该从令牌中的Principal对象复制到HttpContext User对象。我这样检查User对象:

  1. app.Use(async (context, next) =>
  2. {
  3. var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("ClaimsMiddleware");
  4. var user = context.User;
  5. if (user.Identity.IsAuthenticated)
  6. {
  7. logger.LogInformation("Authenticated User Claims:");
  8. foreach (var claim in user.Claims)
  9. {
  10. logger.LogInformation($"{claim.Type}: {claim.Value}");
  11. }
  12. }
  13. else
  14. {
  15. logger.LogInformation("User is not authenticated.");
  16. }
  17. await next.Invoke();
  18. });


所以我想知道我做错了什么,如果我的整个方法只是垃圾,我应该使用不同的库,或者我应该尝试手动复制声明到User对象(如果可能的话)?
此外,这应该是一个多租户API,因此控制器方法需要知道tenant_id声明的值。是否有任何方法可以使tenant_id作为控制器方法的参数,也许通过使用一些装饰器[FromToken],或者这只是我的梦想?或者我必须从HttpContext中检索它,如

  1. var tenantId = HttpContext.User.FindFirst("tenant_id").Value;


在这种情况下,它需要存在于User对象中,而不是...

iibxawm4

iibxawm41#

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

  1. app.UseAuthentication();
  2. app.UseRouting();
  3. app.UseAuthorization();

字符串

相关问题