根据当前 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
属性的东西,但用于声明。 ValueProviderAttribute
和 FromUriAttribute
都继承自 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
之后,我成功地简化了价值提供者的东西
我有一个 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
属性的东西,但用于声明。 ValueProviderAttribute
和 FromUriAttribute
都继承自 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