[ASP.NET MVC]使用IAuthenticationFilter,IAuthorizationFilter实践Form表单登入认证&授权

实际上大多情境并不采用ASP.NET Identity处理身份认证授权,而是选择使用Form表单认证轻量且易于快速实现自订逻辑,网络上也有许多教学如何在ASP.NET中使用FormIdentity。

此篇将提供的是在ASP.NET MVC 5 使用IAuthenticationFilter,IAuthorizationFilter实现Form表单认证的方法


启用Form表单认证

修改Web.config将authentication mode改为Forms表单认证(默认是None)


  
  
    
    
  

注解remove,启用FormsAuthentication模块(ASP.NET 5 项目范本默认移除的)


  
    
  

    使用FormIdentity实践登入逻辑

    通过自订逻辑后,将使用者资讯放入FormsAuthenticationTicketuserData参数,经由加密并建立cookie加入Response回传给Client

    public ActionResult Login(LoginViewModel model, string returnUrl)
    {
        if (!ModelState.IsValid) {
            return View();
        }
        var user = _db.Users
            .FirstOrDefault(x => x.Email == model.Email);
        if (user == null) {
            ModelState.AddModelError("", "请输入正确的账号或密码!");
            return View();
        }
        if (user.Password.Equals(model.Password)) {
            //string roles = string.Join(",", user.Roles.Select(x => x.Name).ToArray());
            var now = DateTime.Now;
            var ticket = new FormsAuthenticationTicket(
                version: 1,
                name: user.Email,
                issueDate: now,
                expiration: now.AddMinutes(30),
                isPersistent: model.RememberMe,
                userData: user.Id.ToString(),//userData:roles,
                cookiePath: FormsAuthentication.FormsCookiePath);
    
            var encryptedTicket = FormsAuthentication.Encrypt(ticket);
            var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            Response.Cookies.Add(cookie);
    
            return RedirectToAction("Index", "Home");
        }
        else {
            ModelState.AddModelError("", "请输入正确的账号或密码!");
            return View();
        }
    }

    IAuthenticationFilter认证过滤器-提供自定义登入认证逻辑

    Authentication filter是ASP.NET MVC 5的新过滤器,可以在Action方法或Controller或全域设置至全部Controller的验证逻辑。

    在ASP.NET MVC 5处理管线中会先执行认证过滤器OnAuthentication(提供AuthenticationContext包含认证主体IPrincipal),进行认证检核。

    另外提供OnAuthenticationChallenge将未经授权的请求进行额外处里(如响应自订ChallengeResult)再响应。

    Lifecycle of an ASP.NET MVC 5 Application

    public class CustomAuthenticationFilter : IAuthenticationFilter
    {
        private readonly Database _db = new Database();
        public void OnAuthentication(AuthenticationContext filterContext)
        {
            if (filterContext.ActionDescriptor.IsDefined(typeof (AllowAnonymousAttribute), inherit: true)
                || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof (AllowAnonymousAttribute), inherit: true)) {
                return;
            }
    
            if (filterContext.Principal.Identity.IsAuthenticated && filterContext.Principal.Identity is FormsIdentity)
            {
                var identity = (FormsIdentity)filterContext.Principal.Identity;
                var ticket = identity.Ticket;
    
                if (!string.IsNullOrEmpty(ticket.UserData)) 
                {
                    //var roles = ticket.UserData.Split(',');
                    //filterContext.Principal = new GenericPrincipal(identity,roles);
                    var user = _db.Users.FirstOrDefault(u => u.Id.ToString() == ticket.UserData);
                    if (user != null) {
                        var roles = user.Roles.Select(r => r.Name).ToArray();
                        filterContext.Principal = new GenericPrincipal(identity, roles);
    
                    }
                }
            }
            else
            {
                filterContext.Result = new HttpUnauthorizedResult();
            }
        }
    
        public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
        {
            if (filterContext.Result == null || filterContext.Result is HttpUnauthorizedResult)
            {
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
                {
                    {"controller","Account"},
                    {"action","Login"},
                    {"returnUrl",filterContext.HttpContext.Request.RawUrl }
                });
            }
            //or do something , add challenge to response 
    
        }
    }

    IAuthorizationFilter授权过滤器-提供自定义身份(角色)授权逻辑

    Authorization filter验证使用者是否拥有权限或角色身份执行Action,ASP.NET MVC 已内建提供实践AuthorizeAttribute只需继承并覆写 AuthorizeCore()方法提供自订逻辑。

    直接继承IAuthorizationFilter界面实践需要考量更多网络安全性的问题,为了避免发生漏洞或是特性不完全,所以Framwork已经提供经由广泛测试且功能完善的良好实践类,只需复用即可。白话:避免明明是自己写不好,却要怪是微软的漏洞xDD

    Github:AuthorizeAttribute.cs 有兴趣或有需要自制可以前往察看源代码

    
    public class CustomAuthorizationFilter : AuthorizeAttribute
    {
        private readonly Database _db = new Database();
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext.Request.IsLocal) {
                return true;
            }
            var identity = httpContext.User.Identity as FormsIdentity;
            if (identity ?.Ticket != null)
            {
                //var userRoles = identity.Ticket.UserData.Split(',');
                var userRoles = _db.Users.Find(identity.Ticket.UserData) ?.Roles.Select(r => r.Name);
                
                if (userRoles ?.Intersect(Roles.Split(',')).Any() ?? false)//交集->具有某一角色->有权限
                {
                    return true;
                }
                //or get current action's roles from db and check the authorization
            }
    
    
            return false;
        }
    }

      补充运行流程图:

      转载自:一张图看懂ASP.NET MVC5认证和授权过滤器的执行顺序

      FilterConfig.cs全域注册过滤器

      注册后即可达到全站套用过滤器,搭配[AllowAnonymous] 属性控制各别Action功能是否需要检查授权。

      public class FilterConfig
      {
          public static void RegisterGlobalFilters(GlobalFilterCollection filters)
          {
              filters.Add(new HandleErrorAttribute());
              filters.Add(new CustomAuthenticationFilter());
              //filters.Add(new CustomAuthorizationFilter());
          }
      }

      额外补充:

      • 登出

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();
            return RedirectToAction("Index", "Home");
        }
      • 3A(AAA) - Authentication、Authorization、Accounting

      参考&延伸阅读

      • mrkt 的程序学习笔记: ASP.NET MVC 实做具有多个角色权限的登入功能

      • 如何在ASP.NET MVC 加上简易表单验证| Yowko's Notes

      • KingKong Bruce记事: ASP.NET 2.0 表单身份认证心得笔记(详述Form认证运行原理)