根据当前 Principal 注入路由值

Injecting route values based on the current Principal

我有一个 Web api,它接受通过 api 密钥、用户访问令牌或客户端访问令牌进行的身份验证。我已经为每个案例编写了一个 DelegatingHandler,它根据给定的身份验证详细信息创建了一个新的 ClaimsPrincipal,并确认主体可以在控制器操作中访问。

我现在要做的是将公司、用户或发布者注入路由值,这样我就可以为每种情况在控制器上创建重载。我需要扩展什么 class/interface 才能插入具有以下条件的管道:

编辑

我不想在此处回避路由 - 我仍然希望 MVC 根据路由值为我选择一条路由。我只想在选择之前向路由值添加一个参数,该参数将具有不同的名称和类型,具体取决于是使用用户访问令牌还是使用 api 密钥。

我认为身份验证的主题应该有两种不同的方法,因为 api 密钥可以访问公司的所有资源,而用户访问令牌只能访问他们被授予权限的资源查看。

我看不出您想在这里使用控制器的原因。您将回避路由,这是 MVC 的一个非常固执己见的部分。我会 create middleware that runs before MVC (它本身只是中间件)。


如果您想影响 RouteData 内联,我会考虑根据您在问题中指定的条件使用 a global IResourceFilter or IAsyncResourceFilter. Then, you can update the RouteData property on the ResourceExecutingContext

您需要确定如何填充 RouteData 属性 的任何其他依赖项都可以按照 the section on dependency injection.

中指定的方式注入到资源过滤器的构造函数中
public class SetRouteValueResourceFilter : IAsyncResourceFilter {

    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) {
        var company = context.HttpContext.Identity.FindFirst("Company")?.Value;

        if (company != null) {
            context.RouteData.Values.Add("company", company);
        }

        await next();
    }

}

这个答案只是一个想法。尽管这是在操作逻辑之前处理的,但我不确定它会影响选择的路线。根据 this diagram,在过滤器 运行.

之前选择路由

我通过自定义 ValueProviderFactory 设法实现了这一点,它从当前委托人的声明中读取值并使它们可用于参数绑定:

public class ClaimsPrincipalValueProviderFactory : ValueProviderFactory
{
    public override IValueProvider GetValueProvider(HttpActionContext actionContext)
    {
        if (actionContext.RequestContext.Principal != null && actionContext.RequestContext.Principal is ClaimsPrincipal principal)
        {
            var pairs = principal.Claims.Select(claim => new KeyValuePair<string, string>(claim.Type, claim.Value));
            return new NameValuePairsValueProvider(pairs, CultureInfo.CurrentCulture);
        }

        return null;
    }
}

为了使用它,可以用ValueProvider属性注释输入参数:

public class FooController : ApiController
{
    [HttpGet]
    public void Bar([ValueProvider(typeof(ClaimsPrincipalValueProviderFactory))]ApiKey apiKey)
    {
        // ...
    }
}

这非常丑陋且不可读,我真正想要的是类似于 FromUri 属性的东西,但用于声明。 ValueProviderAttributeFromUriAttribute 都继承自 ModelBinderAttribute,所以我创建了一个 class 做同样的事情:

/// <summary>
/// The action parameter comes from the user's claims, if the user is authorized.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class FromClaimsAttribute : ModelBinderAttribute
{
    public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
    {
        return parameter.BindWithModelBinding(new ClaimsPrincipalValueProviderFactory());
    }
}

现在 FooController 上的示例方法看起来更具可读性:

[HttpGet]
public void Bar([FromClaims]ApiKey apiKey)
{
    // ...
}

更新

看来还是路由选择和重载有问题,尤其是部分参数为nullable且值为null时。我将不得不继续研究这个。

更新#2

在发现有一个内置的 NameValuePairValueProvider

之后,我成功地简化了价值提供者的东西