我正在尝试编写自己的身份验证中间件.我想创建一个自定义属性来附加到控制器操作(或整个控制器),指定它需要身份验证.然后在请求期间,在我的中间件中,我想交叉引用需要身份验证的操作列表以及适用于此当前请求的操作.我的理解是,我在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 } }
解决方法
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文件的Configure方法中,我进行了设置,以便对我的令牌认证和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.