根据当前Principal

时间:2017-09-29 03:40:47

标签: c# asp.net asp.net-web-api2

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

我现在要做的是将公司,用户或发布者注入路由值,这样我就可以在每个案例的控制器上创建重载。在以下条件下插入管道需要扩展什么类/接口:

  • 可以访问当前主体
  • 可以访问路线数据
  • 尚未选择该操作

修改

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

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

2 个答案:

答案 0 :(得分:1)

我没有看到你想在这里使用控制器的原因。你会回避路由,一个非常自以为是的MVC。我会create middleware that runs before MVC(它本身就是中间件)而不是。

如果您希望内联RouteData,我会考虑使用a global IResourceFilter or IAsyncResourceFilter。然后,您可以根据您在问题中指定的条件更新the RouteData property上的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();
    }

}

这个答案只是一个想法。即使这是在动作逻辑之前处理的,我也不确定它是否会影响选择哪条路线。根据{{​​3}},在运行过滤器之前选择路由。

答案 1 :(得分:1)

我设法通过创建自定义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继承,因此我创建了一个同样的类:

/// <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)
{
    // ...
}

<强>更新

看起来这仍然存在路由选择和重载问题,特别是当某些参数可以为空且值为null时。我将不得不继续研究这个问题。

更新#2

在找到内置NameValuePairValueProvider

之后,我设法简化了价值提供商的工作