授权过滤器—MVC中使用授权过滤器实现JWT权限认证

一、什么是过滤器?

过滤器定义:

        过滤器与中间件很相似,过滤器(Filters)可在管道(pipeline)特定阶段(particular stage)前或后执行操作,可以将过滤器视为拦截器(interceptors)。在.NET MVC开发中,权限验证是非常重要的一部分。通过使用授权过滤器可以很方便地实现权限验证功能。这篇主要分享授权过滤器的使用。.

 二、过滤器的种类:

        过滤器总共有五种,Authorization Filter(授权过滤器),Resource Filter(资源过滤器),Action Filter(操作过滤器),Exception Filter(异常过滤器),Result Filter(结果过滤器)。它们在管道里有先后的执行顺序,最先执行的是授权过滤器。

授权过滤器—MVC中使用授权过滤器实现JWT权限认证

授权过滤器:

最先执行,用于判断用户是否授权。如果未授权,则直接结束当前请求。这种类型的过滤器实现了 IAsyncAuthorizationFilter IAuthorizationFilter 接口。

资源过滤器:

在Authorization过滤器后执行,并在执行其他过滤器 (除Authorization过滤器外)之前和之后执行。由于它在Action之前执行,因而可以用来对请求判断,根据条件来决定是否继续执行Action。这种类型过滤器实现了 IAsyncResourceFilter 或 IResourceFilter 接口。

操作过滤器:

在Action执行的前后执行。与Resource过滤器不一样,它在模型绑定后执行。这种类型的过滤器实现了 IAsyncActionFilter 或 IActionFilter 接口。

异常过滤器:

异常过滤器用于管理未处理的异常,比如:用于捕获异常。这种类型的过滤器实现了 IAsyncExceptionFilter 或 IExceptionFilter 接口。

结果过滤器:

在 IActionResult 执行的前后执行,使用它能够控制Action的执行结果,比如:格式化结果等。需要注意的是,它只有在Action方法成功执行完成后才会运行。这种类型过滤器实现了 IAsyncResultFilter 或 IResultFilter 接口。

授权过滤器—MVC中使用授权过滤器实现JWT权限认证

三、过滤器的级别范围:

        过滤器的级别总共有三种,分别是全局级别过滤器(Global scope)、控制器级别过滤器(Controller scope)和动作级别过滤器(Action scope)。

全局级别过滤器:

        这意味着过滤器覆盖了整个MVC管道。对特定MVC路由的每次调用都将通过该过滤器。在Startup类的ConfigureServices方法中把过滤器添加到全局过滤器集合。
代码实现:
services.AddMvc(options =>{//全局级别过滤器:添加到全局过滤器集合options.Filters.Add<MyAuthorizeFilterAttribute>();//此处必须过滤器名称全部书写,不能省略后缀Attribute,还有就是使用全局过滤器可以不继承特性Attribute});

控制器级别过滤器:

        在这种情况下,过滤器被初始化为一个或多个控制器类中的属性。它将仅对已定向到目标控制器的请求采取行动。定义时需要继承抽象类Attribute,使用时需要的Controller上打上特性标签。
代码实现:
   /// <summary>    /// 授权过滤器测试    /// </summary>    public class AuthFilterTestController : ControllerBase    {              /// <summary>        /// 授权过滤器测试        /// </summary>        /// <returns></returns>        [HttpPost]        [Route("Test")]        public ActionResult AuthTest()        {            return Ok(new { IsSucesss =true, Msg="授权过滤器测试成功" });          }    }

动作级别过滤器:

        筛选器在一个或多个操作方法中初始化为属性。它将仅对已定向到目标方法的请求执行操作。定义时需要继承抽象类Attribute,使用时需要的Action上打上特性标签
代码实现:
   /// <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验证。

授权过滤器—MVC中使用授权过滤器实现JWT权限认证

  /// <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验证失败:您没有权限调用此接口,请登录获取Token                        context.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访问:

授权过滤器—MVC中使用授权过滤器实现JWT权限认证

授权访问:

获取Token:

授权过滤器—MVC中使用授权过滤器实现JWT权限认证

携带Token访问:

授权过滤器—MVC中使用授权过滤器实现JWT权限认证