我有一个简单的 ASP.NET Core http API,并且有很多控制器操作都是这样开始的:
public async Task<ActionResult> Delete()
{
if (!User.Claims.TryGetClaim("merchant_id", out long merchantId))
{
return BadRequest();
}
/* Real code using merchantId */
}
我想减少每次索赔检查的重复,但我不确定如何去做。我已经得到了尽可能小的代码,只使用扩展方法,但我想让它更小,像这样:
public async Task<ActionResult> Delete([FromClaims] long merchantId)
{
/* Real code using merchantId */
}
但我一直在阅读有关 ASP.NET 核心中间件的文档,但我不知道我必须实施什么才能实现这一目标。
内置的
FromBody
属性继承自IBindingSourceMetadata
,它定义了一个BindingSource
属性。但是,我无法在网上找到任何关于如何实现自己的绑定源以从用户声明中获取项目的资源。
您可以使用带有属性的策略基础授权。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy("ShouldHaveMerchantId", policy =>
policy.RequireClaim("merchant_id"));
});
...
}
并且在您的操作方法上使用 Authorize 属性和您的策略名称。
[Authorize(Policy = "ShouldHaveMerchantId")]
public async Task<IActionResult> YourActionMethod()
{
//Your logic
}
你可以像这样创建一个中间件
public class MyMiddleware
{
private readonly RequestDelegate next;
public MyMiddleware (RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext ctx)
{
// Your check here, something like
if (!ctx.User.Claims.TryGetClaim("merchant_id", out long merchantId))
{
ctx.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
else
{
await next.Invoke(ctx);
}
}
}
然后像这样注册
app.UseMiddleware<MyMiddleware>();
我不确定问题是关于哪个版本的,但至少在 ASP.NET Core 6 和 7 中你可以创建一个自定义值提供程序和一个像这样的属性:
public class ClaimsValueProvider : BindingSourceValueProvider
{
private readonly ClaimsPrincipal _principal;
public ClaimsValueProvider(BindingSource bindingSource, ClaimsPrincipal principal) : base(bindingSource)
{
_principal = principal;
}
public override bool ContainsPrefix(String prefix)
{
return _principal.HasClaim(claim => claim.Type == prefix);
}
public override ValueProviderResult GetValue(String key)
{
var claims = _principal.FindAll(key).Select(claim => claim.Value).ToArray();
return claims.Any()
? new ValueProviderResult(claims, CultureInfo.InvariantCulture)
: ValueProviderResult.None;
}
}
public class ClaimsValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.ValueProviders.Add(new ClaimsValueProvider(ClaimsBindingSource.BindingSource, context.ActionContext.HttpContext.User));
return Task.CompletedTask;
}
}
using Microsoft.AspNetCore.Mvc.ModelBinding;
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class FromClaimAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
public FromClaimAttribute()
{
}
public FromClaimAttribute(String claimType)
{
Name = claimType;
}
public String? Name { get; }
public BindingSource BindingSource => ClaimsBindingSource.BindingSource;
}
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class ClaimsBindingSource : BindingSource
{
private ClaimsBindingSource() : base("Claims", "Claims", false, true)
{
}
public static ClaimsBindingSource BindingSource => new();
}
您必须像这样将 ClaimsValueProviderFactory 添加到您的配置中:
builder.Services.AddControllers(options => options.ValueProviderFactories.Add(new ClaimsValueProviderFactory()))
如果你也想从 OpenAPI 和 Swagger 中删除它,你可以创建这个过滤器:
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class FromClaimOpenApiParameterRemover : IOperationFilter
{
public void Apply(OpenApiOperation? operation, OperationFilterContext? context)
{
if (operation == null || context?.ApiDescription?.ParameterDescriptions == null)
{
return;
}
var parametersToHide = context.ApiDescription.ParameterDescriptions
.Where(ParameterHasIgnoreAttribute)
.Join(operation.Parameters, x => x.Name, y => y.Name, (_, y) => y)
.ToList();
if (!parametersToHide.Any())
{
return;
}
foreach (var parameter in parametersToHide)
{
operation.Parameters.Remove(parameter);
}
}
private static bool ParameterHasIgnoreAttribute(ApiParameterDescription parameterDescription)
{
if (parameterDescription.ModelMetadata is not DefaultModelMetadata metadata)
{
return false;
}
var attributes = metadata.Attributes.Attributes
.Union(metadata.Attributes.ParameterAttributes ?? new List<Object>())
.Union(metadata.Attributes.PropertyAttributes ?? new List<Object>());
return attributes.Any(x => x.GetType() == typeof(FromClaimAttribute));
}
}
像这样添加到Swagger生成中:
builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<FromClaimOpenApiParameterRemover>();
});