zoukankan      html  css  js  c++  java
  • 权限管理学习 一、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();
    }
    

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

     

    推荐阅读:

  • 相关阅读:
    文字上下滚动效果
    导航点击变化
    腾讯新闻导航栏
    Docker 数据卷和DockerFile
    Docker 容器
    Docker 镜像
    Docker的初始和架构
    面向对象的设计模式与原则
    安装CentOS 7MInimal版本
    ASP.NET WebApi 启动默认的HelpPage文档注释
  • 原文地址:https://www.cnblogs.com/zhaopei/p/authorize-1.html
Copyright © 2011-2022 走看看