一、什么是过滤器?
过滤器定义:
过滤器与中间件很相似,过滤器(Filters)可在管道(pipeline)特定阶段(particular stage)前或后执行操作,可以将过滤器视为拦截器(interceptors)。在.NET MVC开发中,权限验证是非常重要的一部分。通过使用授权过滤器可以很方便地实现权限验证功能。这篇主要分享授权过滤器的使用。.
二、过滤器的种类:
过滤器总共有五种,Authorization Filter(授权过滤器),Resource Filter(资源过滤器),Action Filter(操作过滤器),Exception Filter(异常过滤器),Result Filter(结果过滤器)。它们在管道里有先后的执行顺序,最先执行的是授权过滤器。

授权过滤器:
最先执行,用于判断用户是否授权。如果未授权,则直接结束当前请求。这种类型的过滤器实现了 IAsyncAuthorizationFilter 或IAuthorizationFilter 接口。
资源过滤器:
在Authorization过滤器后执行,并在执行其他过滤器 (除Authorization过滤器外)之前和之后执行。由于它在Action之前执行,因而可以用来对请求判断,根据条件来决定是否继续执行Action。这种类型过滤器实现了 IAsyncResourceFilter 或 IResourceFilter 接口。
操作过滤器:
在Action执行的前后执行。与Resource过滤器不一样,它在模型绑定后执行。这种类型的过滤器实现了 IAsyncActionFilter 或 IActionFilter 接口。
异常过滤器:
异常过滤器用于管理未处理的异常,比如:用于捕获异常。这种类型的过滤器实现了 IAsyncExceptionFilter 或 IExceptionFilter 接口。
结果过滤器:
在 IActionResult 执行的前后执行,使用它能够控制Action的执行结果,比如:格式化结果等。需要注意的是,它只有在Action方法成功执行完成后才会运行。这种类型过滤器实现了 IAsyncResultFilter 或 IResultFilter 接口。

三、过滤器的级别范围:
过滤器的级别总共有三种,分别是全局级别过滤器(Global scope)、控制器级别过滤器(Controller scope)和动作级别过滤器(Action scope)。
全局级别过滤器:
services.AddMvc(options =>{//全局级别过滤器:添加到全局过滤器集合options.Filters.Add<MyAuthorizeFilterAttribute>();//此处必须过滤器名称全部书写,不能省略后缀Attribute,还有就是使用全局过滤器可以不继承特性Attribute});
控制器级别过滤器:
/// <summary>/// 授权过滤器测试/// </summary>public class AuthFilterTestController : ControllerBase{/// <summary>/// 授权过滤器测试/// </summary>/// <returns></returns>[HttpPost][Route("Test")]public ActionResult AuthTest(){return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });}}
动作级别过滤器:
/// <summary>/// 授权过滤器测试/// </summary>//[MyAuthorizeFilterAttribute]//控制器级别过滤器[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略[Route("api/Auth")]public class AuthFilterTestController : ControllerBase{/// <summary>/// 授权过滤器测试/// </summary>/// <returns></returns>//[MyAuthorizeFilterAttribute]//动作级别过滤器[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略[HttpPost][Route("Test")]public ActionResult AuthTest(){return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });}}
四、定义授权过滤器:
创建JwtHelper帮助类:
安装NuGet包JWT,用于生成token和进行token验证。

/// <summary>/// JWT帮助类/// </summary>public class JwtHelper{//私钥appsettings.json中配置private static string secret = Helper.ConfigHelper.GetSectionValue("TokenSecret");/// <summary>/// 生成JwtToken/// </summary>/// <param name="payload">不敏感的用户数据</param>/// <returns></returns>public static string SetJwtEncode(Dictionary<string, object> payload){IJwtAlgorithm algorithm = new HMACSHA256Algorithm();IJsonSerializer serializer = new JsonNetSerializer();IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);var token = encoder.Encode(payload, secret);return token;}/// <summary>/// 根据jwtToken获取实体/// </summary>/// <param name="token">jwtToken</param>/// <returns></returns>public static LoginUserInfo GetJwtDecode(string token){LoginUserInfo loginUserInfo = new LoginUserInfo();try{IJsonSerializer serializer = new JsonNetSerializer();IDateTimeProvider provider = new UtcDateTimeProvider();IJwtValidator validator = new JwtValidator(serializer, provider);IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();var algorithm = new HMACSHA256Algorithm();IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);loginUserInfo = decoder.DecodeToObject<LoginUserInfo>(token, secret, verify: true);//token为之前生成的字符串return loginUserInfo;}catch (System.Exception ex){return loginUserInfo;}}/// <summary>/// 登录人员信息/// </summary>public class LoginUserInfo{/// <summary>/// 用户名/// </summary>public string username { get; set; }/// <summary>/// 密码/// </summary>public string pwd { get; set; }/// <summary>/// Token 过期时间/// </summary>public double exp { get; set; }}/// <summary>///获取返回Token信息/// </summary>public class TokenResult{/// <summary>/// 是否成功/// </summary>public bool success { get; set; }/// <summary>/// Token字符串/// </summary>public string token { get; set; }/// <summary>/// 提示信息/// </summary>public string message { get; set; }}}
创建ConfigHelper帮助类:
读取加密key值,用于进行生成token时加密和验证token时校验。
/// <summary>/// 配置文件帮助类/// </summary>public static class ConfigHelper{private static IConfiguration _configuration;static ConfigHelper(){//在当前目录或者根目录中寻找appsettings.json文件var fileName = "appsettings.json";var directory = AppContext.BaseDirectory;directory = directory.Replace("\\", "/");var filePath = $"{directory}/{fileName}";if (!File.Exists(filePath)){var length = directory.IndexOf("/bin");filePath = $"{directory.Substring(0, length)}/{fileName}";}var builder = new ConfigurationBuilder().AddJsonFile(filePath, false, true);_configuration = builder.Build();}public static string GetSectionValue(string key){return _configuration.GetSection(key).Value;}/// <summary>/// 解析数据库字符串/// </summary>/// <param name="parameter"></param>/// <param name="providerName"></param>/// <returns></returns>public static string GetParameterValue(string parameter, string connectionString){var vars = connectionString.Split(';');for (var i = 0; i < vars.Length; i++){var pair = vars[i].Split('=');if (pair[0] == parameter) { return pair[1]; }}return "";}}
appsettings.json添加配置节点:
配置文件中,根节点添加节点TokenSecret。
{"Logging": {"LogLevel": {"Default": "Warning"}},"AllowedHosts": "*","TokenSecret": "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk"//添加加密key值}
定义Filter:
继承类Attribute, 接口IAuthorizationFilter ,然后实现接口OnAuthorization方法,书写自定义业务逻辑。
/// <summary>/// 授权过滤器/// </summary>public class MyAuthorizeFilterAttribute : Attribute, IAuthorizationFilter{#region IAuthorizationFilter 授权过滤器/// <summary>/// 授权过滤器实现方法/// </summary>/// <param name="context"></param>public void OnAuthorization(AuthorizationFilterContext context){var authHeader = context.HttpContext.Request.Headers["Authorization"];if (string.IsNullOrWhiteSpace(authHeader)){//var json = JsonConvert.SerializeObject(new OperationResult(OperationResultType.Error, "保存失败"));//此接口必须携带token访问!context.Result = new ContentResult(){Content = "此接口必须携带token访问,请登录携带Token访问",ContentType = "text/html"//application/json};}else //字段值不为空{authHeader = authHeader.ToString().Replace("Bearer", "").Trim();//去掉Bearer字串if (authHeader == ""){context.Result = new ContentResult(){Content = "token验证失败:您没有权限调用此接口,请登录重新获取Token",ContentType = "text/html"};}else{LoginUserInfo LoginInfo = JwtHelper.GetJwtDecode(authHeader);string UserName = LoginInfo.username;string PassWord = LoginInfo.pwd;double ExpireTimeStamp = LoginInfo.exp;if (isTokenExpire(ExpireTimeStamp)) //这里应该验证有效期{//token验证失败:您没有权限调用此接口,请登录获取Tokencontext.Result = new ContentResult(){Content = "token验证失败:您没有权限调用此接口,请登录重新获取Token",ContentType = "text/html"};}}}}#endregion/// <summary>/// 时间戳字符串/// </summary>/// <param name="timestamp"></param>/// <returns></returns>private bool isTokenExpire(double timestamp){try{System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));//当地时区var expireTime = startTime.AddSeconds(timestamp);if (expireTime > DateTime.Now){return false;//未过期}else{return true;//已过期}}catch (Exception ex){return true;}}}
五、定义接口:
获取token接口:
/// <summary>/// JWT/// </summary>[Route("api/Jwt")]public class JwtController : ControllerBase{/// <summary>/// 创建JwtToken/// </summary>/// <param name="username"></param>/// <param name="pwd"></param>/// <returns></returns>[HttpGet][AllowAnonymous][Route("getToken")]public ActionResult CreateToken(string username, string pwd){TokenResult result = new TokenResult();result.token = "";result.success = false;if (username != "" && pwd != ""){IDateTimeProvider provider = new UtcDateTimeProvider();var now = provider.GetNow();var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);var secondsSinceEpoch = Math.Round((now - unixEpoch).TotalSeconds);var payload = new Dictionary<string, object>{{ "username",username },{ "pwd", pwd },{ "exp", secondsSinceEpoch + 60*30} //失效时间秒 此处设置30分钟};result.token = SetJwtEncode(payload);result.success = true;result.message = "生成token成功";}else{result.message = "生成token失败";}return Ok(result);}}
http测试访问接口:
/// <summary>/// 授权过滤器测试/// </summary>//[MyAuthorizeFilterAttribute]//控制器级别过滤器//[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略[Route("api/Auth")]public class AuthFilterTestController : ControllerBase{/// <summary>/// 授权过滤器测试/// </summary>/// <returns></returns>//[MyAuthorizeFilterAttribute]//动作级别过滤器[MyAuthorizeFilter] //备注:特性后缀Attribute也可以省略[HttpPost][Route("Test")]public ActionResult AuthTest(){return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });}}
六、验证:
根据上面测试接口的特性标签位置,此处进行验证使用动作级别过滤器进行验证,只对这一个接口进行授权校验。
未授权访问:
不携带Token访问:

授权访问:

