C# MVC 自定义 用户登录

         很多的业务系统都会有自己原有的用户体系,甚至当你自己写一套应用程序时,也会自定义自己的用户体系(asp.net内置的那套根本就不够用啊,而且扩展也不方便,各种修改和继承)。

        那么,我们究竟有没有更简单的方式来实现用户登录认证呢?不需要哪种各种修改的。身为直男的我们,直来直去习惯了,所以这段时间的搜索总是不免各种被绕碰壁,唯一庆幸的是,我们依旧还是那么直啊。为了坚持这个直下去个性,我们就去寻找一条捷径吧。

        在这里,首先要感谢以下博文链接的作者,这个写得非常精炼,那么,我就在这个大大的基础上进行记录讲解:

https://www.cnblogs.com/chenwolong/p/Authorize.html

        做过三层架构的童鞋都知道,如果要验证/授权一个用户登录,我们一般采取如下方法:

           1、用户输入用户名、密码进行登录

           2、账户/密码正确,保存用户信息至Cookies或者Session,跳转至主页面

           3、在主页面继承BasePage类,并在Page_Load的时候,判断Cookies或者Session的值,如果Cookies或者Session中存放的值在数据库验证成功,则可以观看主页面信息,如果验证不成功,或者Session/Cookies值为NULL时,直接执行语句:Response.Redirect(../Login.aspx);

        上述方法很简单,功能上也实现了登录的认证与授权,相信很多人写的项目都会采取此方法进行判断,殊不知:这种方法安全系数较低,而且违背了NET的登录授权与验证原则。那么NET中的授权与验证又是什么呢?

        .net中的认证(authentication)与授权(authorization)

        认证(authentication) 就是 :判断用户有没有登录,用户输入的账户密码等信息是否通过数据库比对

        授权(authorization) 就是:用户登录后的身份/角色识别,用户通过认证后<登陆后>我们记录用户的信息并授权

           .net中与"认证"对应的是IIdentity接口,而与"授权"对应的则是IPrincipal接口,这二个接口的定义均在命名空间System.Security.Principal中,详情如下:

        用户认证接口:

namespace System.Security.Principal
{
  /// <summary>定义标识对象的基本功能。</summary>
  [ComVisible(true)]
  [__DynamicallyInvokable]
  public interface IIdentity
  {
    /// <summary>获取当前用户的名称。</summary>
    /// <returns>用户名,代码当前即以该用户的名义运行。</returns>
    [__DynamicallyInvokable]
    string Name { [__DynamicallyInvokable] get; }

    /// <summary>获取所使用的身份验证的类型。</summary>
    /// <returns>用于标识用户的身份验证的类型。</returns>
    [__DynamicallyInvokable]
    string AuthenticationType { [__DynamicallyInvokable] get; }

    /// <summary>获取一个值,该值指示是否验证了用户。</summary>
    /// <returns>如果用户已经过验证,则为 <see langword="true" />;否则为 <see langword="false" /></returns>
    [__DynamicallyInvokable]
    bool IsAuthenticated { [__DynamicallyInvokable] get; }
  }
}

        用户授权接口:

namespace System.Security.Principal
{
  /// <summary>定义主体对象的基本功能。</summary>
  [ComVisible(true)]
  [__DynamicallyInvokable]
  public interface IPrincipal
  {
    /// <summary>获取当前用户的标识。</summary>
    /// <returns>与当前用户关联的 <see cref="T:System.Security.Principal.IIdentity" /> 对象。</returns>
    [__DynamicallyInvokable]
    IIdentity Identity { [__DynamicallyInvokable] get; }

    /// <summary>确定当前用户是否属于指定的角色。</summary>
    /// <param name="role">要检查其成员资格的角色的名称。</param>
    /// <returns>如果当前用户是指定角色的成员,则为 <see langword="true" />;否则为 <see langword="false" /></returns>
    [__DynamicallyInvokable]
    bool IsInRole(string role);
  }
}

        应该注意到:IPrincipal接口中包含着一个只读的IIdentity

           1、IIdentity 接口中属性的含义:

          AuthenticationType  验证方式:NET中有Windows、Forms、Passport三种验证方式,其中以Forms验证用的最多,本节讲解的MVC验证与授权就是基于Form。

           IsAuthenticated  是否通过验证,布尔值

           Name  用户登录的账户名称

          2、IPrincipal 接口中属性的含义:

          IIdentity  上述IIdentity接口的实例

           IsInRole  布尔值,验证用户权限

        下面我们对HttpContext封闭类刨根问底

    /// <summary>清除当前 HTTP 请求的所有错误。</summary>
    public void ClearError()
    {
      if (this._tempError != null)
        this._tempError = (Exception) null;
      else
        this._errorCleared = true;
      if (!this._isIntegratedPipeline || this._notificationContext == null)
        return;
      this._notificationContext.Error = (Exception) null;
    }

    /// <summary>获取或设置当前 HTTP 请求的安全信息。</summary>
    /// <returns>当前 HTTP 请求的安全信息。</returns>
    public IPrincipal User
    {
      get
      {
        return this._principalContainer.Principal;
      }
      [SecurityPermission(SecurityAction.Demand, ControlPrincipal = true)] set
      {
        this.SetPrincipalNoDemand(value);
      }
    }

    IPrincipal IPrincipalContainer.Principal { get; set; }

        HttpContext.User本身就是一个IPrincipal接口的实例。

        有了上面的预备知识,我们在程序中处理认证问题时,只需对HttpContext.User赋值即可。

        下面是小弟模拟的一个授权方法

/// <summary>
        /// 模拟授权  此处不经过验证 直接授权访问 UserCenter() 
        /// </summary>
        /// <returns></returns>
        public ActionResult Login()
        {
            GenericIdentity _identity = new GenericIdentity("User", "Forms");
            GenericPrincipal _principal = new GenericPrincipal(_identity, new string[] { "admins", "vips" });
            HttpContext.User = _principal;
            return RedirectToAction("UserCenter");
        }

        [Authorize(Roles = "admins")]  
        public ActionResult UserCenter()
        {
            return View();
        }

        上面的方法中,一旦运行登陆页,不需要填账户密码,直接会访问UserCenter(),即:直接会跳转到个人中心

        好啦,如果上边的例子大家看懂了,那么下面的登录其实就很简单了,和上述所讲一样:由于 HttpContext.User 本身就是一个IPrincipal接口的实例!因此:我们只需给这个实例赋值即可,上面的例子就证实了这一点。

        咱们回到开篇的描述,一般我们都是通过Session或者cookies就行Forms验证登录,那么在MVC中我们应当怎么做呢?

 

1.MVCForms验证

   Forms验证在内部的机制为把用户数据加密后保存在一个基于cookie的票据FormsAuthenticationTicket中,因为是经过特殊加密的,所以应该来说是比较安全的。而.net除了用这个票据存放自己的信息外,还留了一个地给用户自由支配,这就是现在要说的UserData。

   UserData可以用来存储string类型的信息,并且也享受Forms验证提供的加密保护,当我们需要这些信息时,也可以通过简单的get方法得到,兼顾了安全性和易用性,用来保存一些必须的敏感信息还是很有用的。

在此,本案例中UserData用于存储用户角色值,admins/vips/superadmins等,这些值大家可根据系统需求自行定义。本案例中是写死的,当然,在实际项目中应当根据登录账户信息结合数据库进行赋值。

  2FormsAuthenticationTicket基于forms的验证

   构建基于forms的验证机制过程如下:
1,设置IIS为可匿名访问和asp.net web.config中设置为form验证,配置webConfig,如下:

    <!--用户没有授权时 自动退回登陆界面-->
    <authentication mode="Forms">
      <forms loginUrl="~/Home/Login" timeout="2880" path="/" />
    </authentication>

2,检索数据存储验证用户,并检索角色(其实就是登录验证)


3,使用FormsAuthenticationTicket创建一个Cookie并回发到客户端,并存储

关于2、3两步,我以代码示例:

我创建的登录Model如下:

public class UserLogin
    {
        private readonly object LOCK = new object();
        Maticsoft.BLL.YX_UserInfo Bll = new Maticsoft.BLL.YX_UserInfo();
        Maticsoft.Model.YX_UserInfo Mol = new Maticsoft.Model.YX_UserInfo();
        //
        [Required(ErrorMessage = "请输入账户号码/手机号")]
        [RegularExpression(@"^1[3458][0-9]{9}$", ErrorMessage = "手机号格式不正确")]
        public string UserName { get; set; }

        [Required(ErrorMessage="请输入账户密码")]
        [DataType(DataType.Password,ErrorMessage="密码格式不正确")]
        [MinLength(6, ErrorMessage = "密码长度介于6~15位之间")]
        [MaxLength(15, ErrorMessage = "密码长度介于6~15位之间")]
        public string UserPwd { get; set; }

        public bool LoginAction()
        {
            lock (LOCK)
            {
                DataTable dt = Bll.GetList(" Uname='" + CommonMethod.CheckParamThrow(UserName) + "' and Upwd='" + CommonMethod.Md532(UserPwd) + "' and flat1=1").Tables[0];
                if (dt.Rows.Count > 0)
                {
                    FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                           1,
                           UserName,
                           DateTime.Now,
                           DateTime.Now.AddMinutes(30),
                           false,
                           "admins,vip",
                           "/"
                           );
                    string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
                    System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
                    System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
    }

 

数据库验证成功,我们将用户信息存入Cookies,

然后我们在Global中解析这个Cookies,然后把解析的结果赋值给:HttpContext.User

代码如下:

/// <summary>
        /// 登录验证、s授权
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Application_AuthenticateRequest(Object sender, EventArgs e)
        {
            string cookieName = FormsAuthentication.FormsCookieName;
            HttpCookie authCookie = Context.Request.Cookies[cookieName];
            FormsAuthenticationTicket authTicket = null;
            try
            {
                authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            }
            catch (Exception ex)
            {
                return;
            }
            string[] roles = authTicket.UserData.Split(',');
            FormsIdentity id = new FormsIdentity(authTicket);
            GenericPrincipal principal = new GenericPrincipal(id, roles);
            Context.User = principal;//存到HttpContext.User中     
        }

 

Context.User一旦被赋值成功,我们就可以访问那些有权限限制的方法。代码如下:

        [Authorize(Roles = "admins")]  
        public ActionResult UserCenter()
        {
            return View();
        }

SO,搞懂了本质,一切都可以解决。

补充:本人登录代码如下:

前端 Login.cshtml

@{
    ViewBag.Title = "登录";
}
@section css
{
    <link href="~/Content/login.css" rel="stylesheet" />
}
<form id="form1">
<div class="wrap loginBox">
    <div class="logo"><img src="/images/logo.png"/></div>
    <div class="head">
        <ul>
            <li><input type="text" id="UserName" name="UserName" class="text n2" placeholder="手机号码" /></li>
            <li><input type="password" id="UserPwd" name="UserPwd" class="text n3" placeholder="密码" /></li>
        </ul>
    </div>
    <div class="body">
        <a href="JavaScript:void(0)" class="btn btn-blue" onclick="Login()">立即登录</a>
        @*<a href="JavaScript:void(0)" class="btn btn-yell">逗包账号登录</a>*@
    </div>
    <div class="foot">
        <a href="JavaScript:void(0)">忘记密码?</a><a href="JavaScript:void(0)">账号注册</a>
    </div>
</div>
</form>
<script type="text/javascript">
    function Login() {
        var UserName = $("#UserName").val();
        var UserPwd = $("#UserPwd").val();
        $(document).ready(function (data) {
            $.ajax({
                url: "/home/Login",
                type: "post",
                contentType: "application/json",
                dataType: "text",
                data: JSON.stringify({ UserName: UserName, UserPwd: UserPwd }),
                success: function (result, status) {
                    if (result == "200") {
                        //登录成功 跳转
                        location.href = "http://www.baidu.com";
                    }
                    else {
                        alert(result);//弹出错误码
                    }
                },
                error: function (error) {
                    alert(error);
                }
            });
        });
    }
</script>

 

后端 HomeController.cs:

public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return RedirectToAction("Login");
        }

        public ActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public object Login(UserLogin LoginMol)
        {
            if (NetworkHelper.Ping())//判断当前是否有网络
            {
                if (ModelState.IsValid)//是否通过Model验证
                {
                    if (LoginMol.LoginAction())
                    {
                        return 200;
                    }
                    else
                    {
                        return "账户或密码有误";
                    }
                }
                else
                {
                    //读取错误信息
                    StringBuilder sb = new StringBuilder("");
                    var errors = ModelState.Values;
                    foreach (var item in errors)
                    {
                        foreach (var item2 in item.Errors)
                        {
                            if (!string.IsNullOrEmpty(item2.ErrorMessage))
                            {
                                if (string.IsNullOrEmpty(sb.ToString()))
                                {
                                    sb.AppendLine(item2.ErrorMessage);//读取一个错误信息 并返回
                                }
                            }
                        }
                    }
                    return sb.ToString();
                }
            }
            else
            {
                return "当前无网络,请稍后重试";
            }
        }
    }

 


怕博文丢失,所以全部记录下来了。

那么看完上面的解释,我们就尝试自己做一个吧,很简单,我分别标注下代码逻辑:

1. 实现用户Data Object

    public class UserVo
    {
        public string User { get; set; }
        public string Password { get; set; }
    }

2. 实现登录控制器

    public class AccountController : Controller
    {
        [AllowAnonymous]
        // GET: Account/Login
        public ActionResult Login()
        {
            return View();
        }

        // POST: Account/Login
        [HttpPost]
        public string Login(UserVo LoginUser)
        {
            try
            {
                if (ModelState.IsValid && LoginUser != null)
                {
                    using (WfContext db = new WfContext())
                    {
                        //账号密码是否正确
                        LoginVo login = SqlDml.Select<LoginVo>(db, $"N'{LoginUser.User}',N'{LoginUser.Password}'").FirstOrDefault();
                        if (login != null && login.PasswordMatch)
                        {
                            //拥有哪些权限
                            ServerUser su = SqlDml.Select<ServerUser>(db, "").FirstOrDefault();
                            //权限写入Ticket
                            FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, LoginUser.User, DateTime.Now, DateTime.Now.AddMinutes(30), false, (su==null?"": Newtonsoft.Json.JsonConvert.SerializeObject(su)), "/");
                            //Ticket加密
                            string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
                            System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
                            //写Cookie
                            System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
                            return $"登录成功";
                        }
                    }
                }
                
            }
            catch (Exception ex)
            {
                AlertBox.Show(ex.Message);
                return "Error";
            }
            return $"{LoginUser?.User} 登录失败";
        }

    }

    public class HomeController : Controller
    {
        [Authorize(Roles = "admins")]
        // GET: Home/Index
        public string Index()
        {
            return "当前账号已经授权访问";
        }
    }

3. 实现登录视图

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>登录</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @using (Html.BeginForm("Login", "Account", FormMethod.Post,new {@style="margin-left: 20px"}))
    {
        <div class="form-group">
            <label for="User" stype="display:inline;">账户:</label>
            <input type="text" class="form-control" id="User" name="User" style="display: inline; width: 200px;" autocomplete="off" />
        </div>
        <div class="form-group">
            <label for="Password" style="display: inline;">密码:</label>
            <input type="text" class="form-control" id="Password" name="Password" style="display: inline; width: 200px;" autocomplete="off" />
        </div>
        <button type="submit" class="btn btn-primary" style="margin-left: 80px; margin-top: 10px;width: 100px">登录</button>
    }
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
</body>
</html>

4. 实现Global解析Cookie

    public class MvcApplication : System.Web.HttpApplication
    {

        /// <summary>
        /// 登录验证、s授权
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Application_AuthenticateRequest(Object sender, EventArgs e)
        {
            string cookieName = FormsAuthentication.FormsCookieName;
            HttpCookie authCookie = Context.Request.Cookies[cookieName];
            if (authCookie!=null)
            {
                try
                {
                    FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
                    List<string> roles = new List<string>();
                    if (!string.IsNullOrEmpty(authTicket?.UserData))
                    {
                        try
                        {
                            var su= Newtonsoft.Json.JsonConvert.DeserializeObject<Models.ServerUser>(authTicket.UserData);
                            if (su.Admin)
                            {
                                roles.Add("Admin");
                            }
                            if (su.Export)
                            {
                                roles.Add("Export");
                            }
                            if (su.Impersonate)
                            {
                                roles.Add("Impersonate");
                            }
                        }
                        catch
                        {
                            roles.Clear();
                        }
                    }
                    FormsIdentity id = new FormsIdentity(authTicket);
                    GenericPrincipal principal = new GenericPrincipal(id, roles.ToArray());
                    Context.User = principal;//存到HttpContext.User中 
                }
                catch (Exception ex)
                {
                    return;
                }
            }
            
        }

    }

5. 实现授权特性

 

6. 实现外部认证

        private void BtnLogin_Click(object sender, EventArgs e)
        {
            if (Common.Common.NotNull(txtUser.Text,txtPwd.Text))
            {
                System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
                UserVo user = new UserVo() { User = txtUser.Text, Password = txtPwd.Text };
                var s = client.PostAsJsonAsync("http://localhost:50249/Account/Login", user);
                var cookies = s.Result.Headers.GetValues("Set-Cookie");
                foreach (string cookie in cookies)
                {
                    System.Diagnostics.Debug.Print(cookie);
                    Application.Cookies.Add(FormsAuthentication.FormsCookieName, cookie.Replace($"{FormsAuthentication.FormsCookieName}=",""));
                }
                if (s.Result!=null)
                {
                    if (Application.IsAuthenticated)
                    {
                        Parent.Controls.Add(new ctlNavig() { Dock = DockStyle.Fill });
                        this.Dispose();
                    }
                    else
                    {
                        AlertBox.Show($"{txtUser.Text} 用户未经过认证", MessageBoxIcon.Error, null, ContentAlignment.BottomRight, 3000);
                    }
                }
                
            }
            
        }
posted @ 2020-04-02 10:53  devs  阅读(1067)  评论(0编辑  收藏  举报