权限管理之ASP.NET Forms身份认证

Posted DotNet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了权限管理之ASP.NET Forms身份认证相关的知识,希望对你有一定的参考价值。


来源:农码一生

cnblogs.com/zhaopei/p/7257674.html


说明:本文示例使用的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();

}


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



推荐阅读:


http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html


Demo:


https://github.com/zhaopeiym/BlogDemoCode/tree/master/权限管理/1-Forms身份认证


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

以上是关于权限管理之ASP.NET Forms身份认证的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core框架探索之Authentication

Asp.Net Core 项目实战之权限管理系统 无中生有

ASP.net Membership角色与权限管理

net core体系-web应用程序-4asp.net core2.0 项目实战-13基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级

ASP.NET身份认证和权限认证

Asp.net 表单身份验证登录循环