• 权限管理学习 一、ASP.NET Forms身份认证


    说明:本文示例使用的VS2017和MVC5。
    系统无论大小、牛逼或屌丝,一般都离不开注册、登录。那么接下来我们就来分析下用户身份认证。

    简单实现登录、注销

    以前在学习.net的时候不知道什么Forms身份认证,直接用session实现登录,效果也蛮好嘛。而且用户信息存在服务端,安全。
    前端代码:

    @if (string.IsNullOrWhiteSpace(ViewBag.UserName))
    {
        <form action="/home/login1">
            <input type="text" name="userName" />
            <input type="submit" value="登录" />
        </form>
    }
    else
    {
        <form action="/home/logout1">
            <div>当前用户已登录,登录名:@ViewBag.UserName</div>
            <input type="submit" value="退出" />
        </form>
    }
    
    

    后台代码:

    public ActionResult Index()
    {
        ViewBag.UserName = Session["userName"]?.ToString();           
        return View();
    }       
    
    public void Login1(string userName)
    {
        if (!string.IsNullOrWhiteSpace(userName))  //为了方便演示,就不做真的验证了     
            Session["userName"] = userName;
        else
            Session["userName"] = null;
        Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原来页面
    }
    
    public void Logout1()
    {
        Session["userName"] = null;
        Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原来页面
    }
    

    是不是,简单明了。想要自己扩展或是定制什么功能都非常好用。不过我们需要维护session。比如系统重新发布,或者iis被自动重启。就会出现session丢失的情况。也就是用户会莫名其妙提升需要重新登录。体验非常不好。(这里先不讨论session服务和数据库的情况)。既然微软有一套成熟的权限管理我们为什么不用呢?

    Forms认证登录、注销

    首先在web.config里开启Forms身份认证:

    <system.web>
      <authentication mode="Forms"></authentication>
    

    后台代码:

    public void Login2(string userName)
    {
        if (!string.IsNullOrWhiteSpace(userName))  //为了方便演示,就不做真的验证了
            FormsAuthentication.SetAuthCookie(userName, true); //登录
        Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原来页面
    }
    
    public void Logout2()
    {
        FormsAuthentication.SignOut();//登出
        Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原来页面
    }
    

    前台代码:

    @if (!Request.IsAuthenticated)
    {
        <form action="/home/login2">
            <input type="text" name="userName" />
            <input type="submit" value="登录" />
        </form>
    }
    else
    {
        <form action="/home/logout2">
            <div>当前用户已登录,登录名:@Context.User.Identity.Name</div>
            <input type="submit" value="退出" />
        </form>
    }
    

    如此几句代码就实现了我们的登录和注销。和我们自己用session管理登录不同。Forms身份认证是直接把信息存cookie到浏览器的。通过SetAuthCookie这个方法名也可以看出来。不过Cookie信息经过了加密。
    这里有必要说明session和cookie的关系。当我们利用session来维持用户状态的时候,其实也用到了cookie。

    然而Forms身份认证仅仅只是把信息存了cookie,而没有在服务端维护一个对应的session。
    不信你可以测试。可以用两种方式都登录,然后清除session就可以测出来了。(怎么清session?重启iis,或者修改下后台代码在重新编译访问)
    【说明】用户认证为什么要存cookie?因为HTTP是一个无状态的协议。对于服务器来说,每次请求都是一样的。所以,只能通过每次请求带的cookie来识别用户了。(暂时不考虑其他方式)

    自定义的身份认证标识

    上面使用的登录很简单,但实际情况往往很复杂。明显正常业务需要存的用户信息会要更多。那么我们是否可以扩展身份标识呢?答案是肯定的。
    后台代码:

    public void Login3(string userName)
    {
        if (!string.IsNullOrWhiteSpace(userName))  //为了方便演示,就不做真的验证了     
        {
            UserInfo user = new UserInfo()
            {
                Name = userName,
                LoginTime = DateTime.Now
            };
            //1、序列化要保存的用户信息
            var data = JsonConvert.SerializeObject(user);
    
            //2、创建一个FormsAuthenticationTicket,它包含登录名以及额外的用户数据。
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddDays(1), true, data);
    
            //3、加密保存
            string cookieValue = FormsAuthentication.Encrypt(ticket);
    
            // 4. 根据加密结果创建登录Cookie
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue);
            cookie.HttpOnly = true;
            cookie.Secure = FormsAuthentication.RequireSSL;
            cookie.Domain = FormsAuthentication.CookieDomain;
            cookie.Path = FormsAuthentication.FormsCookiePath;
    
            // 5. 写登录Cookie
            Response.Cookies.Remove(cookie.Name);
            Response.Cookies.Add(cookie);
        }
        Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原来页面
    }
    

    然后在Global.asax的Application_AuthenticateRequest方法:

    protected void Application_AuthenticateRequest()
    {
        GetUserInfo();
    }
    
    //通过coolie解密 读取用户信息到 HttpContext.Current.User
    public void GetUserInfo()
    {
        // 1. 读登录Cookie
        HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
    
        try
        {
            UserInfo userData = null;
            // 2. 解密Cookie值,获取FormsAuthenticationTicket对象
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
    
            if (ticket != null && string.IsNullOrEmpty(ticket.UserData) == false)
                // 3. 还原用户数据
                userData = JsonConvert.DeserializeObject<UserInfo>(ticket.UserData);
    
            if (ticket != null && userData != null)
                // 4. 构造我们的MyFormsPrincipal实例,重新给context.User赋值。
                HttpContext.Current.User = new MyFormsPrincipal<UserInfo>(ticket, userData);
        }
        catch { /* 有异常也不要抛出,防止攻击者试探。 */ }
    }
    

    前端代码:

    @{
        MyFormsPrincipal<UserInfo> user = Context.User as MyFormsPrincipal<UserInfo>;
        if (user == null)
        {
            <form action="/home/login3">
                <input type="text" name="userName" />
                <input type="submit" value="登录" />
            </form>
        }
        else
        {
    
            <form action="/home/logout2">
                <div>当前用户已登录,登录名:@Context.User.Identity.Name</div>
                <div>当前用户已登录,登录时间:@user.UserData.LoginTime</div>
                <input type="submit" value="退出" />
            </form>
        }
    }
    

    其实整个过程和FormsAuthentication.SetAuthCookie(userName, true); //登录是等效的。只是我们通过扩展,存了我们想要存储的数据。
    过程也比较简单:

    • 构造要存储的数据
    • 序列化
    • 把序列化信息放入FormsAuthenticationTicket对象
    • 通过FormsAuthentication.Encrypt加密对象
    • 发送cookie到浏览器

    这里稍微复杂点的地方就是解密然后给User赋值HttpContext.Current.User = new MyFormsPrincipal<UserInfo>(ticket, userData);
    MyFormsPrincipal需要实现接口MyFormsPrincipal

    public class MyFormsPrincipal<TUserData> : IPrincipal where TUserData : class, new()
    {
        private IIdentity _identity;
        private TUserData _userData;
    
        public MyFormsPrincipal(FormsAuthenticationTicket ticket, TUserData userData)
        {
            if (ticket == null)
                throw new ArgumentNullException("ticket");
            if (userData == null)
                throw new ArgumentNullException("userData");
    
            _identity = new FormsIdentity(ticket);
            _userData = userData;
        }
    
        public TUserData UserData
        {
            get { return _userData; }
        }
    
        public IIdentity Identity
        {
            get { return _identity; }
        }
    
        public bool IsInRole(string role)//这里暂时不实现
        {
            return false;
        }
    }
    

    倒也没有什么特别,就是实例化的时候传入票据和自定义数据就好了。

    授权

    有了登录一般都离不开授权。微软的东西好就好在,一般都是成套成套的。

    [Authorize]
    public ActionResult LoginOk()
    {
        return View();
    }
    

    直接给Action添加一个Authorize特性就好了,这人就会自动检查是否登录。如果没有登录自动跳转到登录页面。登录页面的设置还是在web.config里面

    <system.web>
      <authentication mode="Forms" >
        <forms loginUrl="/home/index"></forms>
    

    这种简单的授权验证明显是不够的。很多时候某些页面只有某些人才能访问。比如VIP。那么我们又要扩展了。

    //继承 AuthorizeAttribute
    public class MyAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity.Name != "农码一生")
            {
                filterContext.HttpContext.Response.Write("您不是vip用户,不能访问机密数据");
                filterContext.HttpContext.Response.End();
                return;
            }
            base.OnAuthorization(filterContext);
        }
    }
    
    [MyAuthorize]
    public ActionResult LoginVIP()
    {
        return View();
    }
    

    是的,就是这么简单。说了这么多,来张效果图吧:

     

    推荐阅读:

  • 相关阅读:
    第二十九课 循环链表的实现
    第二十八课 再论智能指针(下)
    第二十七课 再论智能指针(上)
    第二十六课 典型问题分析(Bugfix)
    普通new和placement new的重载
    leetcode 581. Shortest Unsorted Continuous Subarray
    leetcode 605. Can Place Flowers
    leetcode 219. Contains Duplicate II
    leetcode 283. Move Zeroes
    leetcode 217. Contains Duplicate
  • 原文地址:https://www.cnblogs.com/zhaopei/p/authorize-1.html
Copyright © 2020-2023  润新知