我正在构建一个ASP.NET 5(vNext)站点,它将托管动态页面,静态内容和REST Web API。我找到了如何使用新的ASP.NET方式创建中间件的示例,但我遇到了麻烦。
我正在尝试编写自己的身份验证中间件。我想创建一个自定义属性来附加到控制器操作(或整个控制器),指定它需要身份验证。然后在请求期间,在我的中间件中,我想交叉引用需要身份验证的操作列表以及适用于此当前请求的操作。我的理解是,我在MVC中间件之前配置我的中间件,以便在管道中首先调用它。我需要这样做,以便在MVC控制器处理请求之前完成身份验证,这样我就无法在必要时阻止控制器被调用。但这不也意味着MVC路由器还没有确定我的路线吗?在我看来,路线的确定和路线行动的执行是在管道中的一个步骤发生的吗?
如果我希望能够确定请求是否与控制器处理请求之前发生的中间件管道步骤中的控制器操作相匹配,那么我是否必须编写自己的URL解析器想出来?在控制器实际处理请求之前,有没有办法获取请求的路由数据?
编辑:我开始认为RouterMiddleware可能是我正在寻找的答案。我假设我可以弄清楚如何让我的路由器选择标准MVC路由器使用的相同路由(我使用属性路由)并让我的路由器(真正的身份验证器)将请求标记为未成功处理身份验证,以便默认的mvc路由器执行实际的请求处理。我真的不想完全实现MVC中间件正在做的所有事情。正在努力弄明白。 RouterMiddleware向我展示了我想要做的事情。
编辑2:这是ASP.NET 5中的中间件模板
public class TokenAuthentication
{
private readonly RequestDelegate _next;
public TokenAuthentication(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
//do stuff here
//let next thing in the pipeline go
await _next(context);
//do exit code
}
}
答案 0 :(得分:3)
我最后查看了ASP.NET源代码(因为它现在是开源的!)并且发现我可以从this class复制UseMvc扩展方法并替换我自己的默认处理程序。 / p>
public static class TokenAuthenticationExtensions
{
public static IApplicationBuilder UseTokenAuthentication(this IApplicationBuilder app, Action<IRouteBuilder> configureRoutes)
{
var routes = new RouteBuilder
{
DefaultHandler = new TokenRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
}
然后您创建自己的this class版本。就我而言,我实际上并不想调用这些动作。我会让典型的Mvc中间件做到这一点。因为在这种情况下,我会删除所有相关代码,并保留我需要获取 actionDescriptor 变量中的路径数据。我可能可以删除处理备份路由数据的代码,因为我不认为我将要做的事情会影响数据,但我已将其保留在示例中。这是我将基于mvc路由处理程序开始的框架。
public class TokenRouteHandler : IRouter
{
private IActionSelector _actionSelector;
public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
EnsureServices(context.Context);
context.IsBound = _actionSelector.HasValidAction(context);
return null;
}
public async Task RouteAsync(RouteContext context)
{
var services = context.HttpContext.RequestServices;
EnsureServices(context.HttpContext);
var actionDescriptor = await _actionSelector.SelectAsync(context);
if (actionDescriptor == null)
{
return;
}
var oldRouteData = context.RouteData;
var newRouteData = new RouteData(oldRouteData);
if (actionDescriptor.RouteValueDefaults != null)
{
foreach (var kvp in actionDescriptor.RouteValueDefaults)
{
if (!newRouteData.Values.ContainsKey(kvp.Key))
{
newRouteData.Values.Add(kvp.Key, kvp.Value);
}
}
}
try
{
context.RouteData = newRouteData;
//Authentication code will go here <-----------
var authenticated = true;
if (!authenticated)
{
context.IsHandled = true;
}
}
finally
{
if (!context.IsHandled)
{
context.RouteData = oldRouteData;
}
}
}
private void EnsureServices(HttpContext context)
{
if (_actionSelector == null)
{
_actionSelector = context.RequestServices.GetRequiredService<IActionSelector>();
}
}
}
最后,在管道末尾的Startup.cs文件的配置方法中,我进行了设置,以便为我的令牌认证和mvc使用相同的路由设置(我使用属性路由)路由器。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//Other middleware delcartions here <----------------
Action<IRouteBuilder> routeBuilder = routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
};
app.UseTokenAuthentication(routeBuilder);
//Middleware after this point will be blocked if authentication fails by having the TokenRouteHandler setting context.IsHandled to true
app.UseMvc(routeBuilder);
}
编辑1: 我还应该注意到,目前我并不担心两次选择路由所需的额外时间,这是我认为会发生的事情,因为我的中间件和Mvc中间件都会这样做。如果这成为性能问题,那么我将在一个处理程序中构建mvc和身份验证。这在性能方面是最好的想法,但我在这里展示的是我认为最模块化的方法。
编辑2: 最后,为了获得我需要的信息,我必须将ActionDescriptor强制转换为ControllerActionDescriptor。我不确定您可以在ASP.NET中执行哪些其他类型的操作,但我非常确定我的所有操作描述符都应该是ControllerActionDescriptors。也许旧的遗留Web Api东西需要另一种类型的ActionDescriptor。