Form表单认证

一、概述

1、理解Http的无状态特性

HTTP是一个无状态的协议,WEB服务器在处理所有传入HTTP请求时,根本就不知道某个请求是否是一个用户的第一次请求与后续请求,或者是另一个用户的请求。 WEB服务器每次在处理请求时,都会按照用户所访问的资源所对应的处理代码,从头到尾执行一遍,然后输出响应内容,WEB服务器根本不会记住已处理了哪些用户的请求,因此,我们通常说HTTP协议是无状态的。

2、为什么需要认证

虽然HTTP协议与WEB服务器是无状态,但我们的业务需求却要求有状态,典型的就是用户登录, 在这种业务需求中,要求WEB服务器端能区分某个请求是不是一个已登录用户发起的,或者当前请求是哪个用户发出的。 在开发WEB应用程序时,我们通常会使用Cookie来保存一些简单的数据供服务端维持必要的状态。

二、Form表单认证

登录的操作通常会检查用户提供的用户名和密码,因此登录状态也必须具有足够高的安全性。 在Forms身份认证中,由于登录状态是保存在Cookie中,而Cookie又会保存到客户端,因此,为了保证登录状态不被恶意用户伪造, ASP.NET采用了加密的方式保存登录状态。 为了实现安全性,ASP.NET采用【Forms身份验证凭据】(即FormsAuthenticationTicket对象)来表示一个Forms登录用户, 加密与解密由FormsAuthentication的Encrypt与Decrypt的方法来实现。

下面通过一张图详细的了解Form表单认证的过程:

Form表单认证

 

 

三、Form表单认证的示例

1、创建mvc项目

Form表单认证

Form表单认证

Form表单认证

 

 

2、mvc项目结构

Form表单认证

 

 

3、Action加入认证

 HomeController中默认提供了几个action,我们加入[Authorize]标识,如下:

using System.Web.Mvc;

namespace FormAuthentication.Controllers
{
    public class HomeController : Controller
    {
        [Authorize]
        public ActionResult Index()
        {
            return View();
        }
        [Authorize]
        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }
        [Authorize]
        public ActionResult Contact()
        {
            ViewBag.Message = "Your contact page.";

            return View();
        }
    }
}

因为路由默认启动Home/Index,所以启动下项目,看下效果:

Form表单认证

 

 

提示没有授权请求home/index。Authorize做了什么,为什么加入[Authorize],action就无权访问了?我们先来分析下Authorize的定义:

namespace System.Web.Mvc
{
    //
    // 摘要:
    //     指定对控制器或操作方法的访问只限于满足授权要求的用户。
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
    {
        //
        // 摘要:
        //     初始化 System.Web.Mvc.AuthorizeAttribute 类的新实例。
        public AuthorizeAttribute();

        //
        // 摘要:
        //     获取或设置有权访问控制器或操作方法的用户角色。
        //
        // 返回结果:
        //     有权访问控制器或操作方法的用户角色。
        public string Roles { get; set; }
        //
        // 摘要:
        //     获取此特性的唯一标识符。
        //
        // 返回结果:
        //     此特性的唯一标识符。
        public override object TypeId { get; }
        //
        // 摘要:
        //     获取或设置有权访问控制器或操作方法的用户。
        //
        // 返回结果:
        //     有权访问控制器或操作方法的用户。
        public string Users { get; set; }

        //
        // 摘要:
        //     在过程请求授权时调用。
        //
        // 参数:
        //   filterContext:
        //     筛选器上下文,它封装有关使用 System.Web.Mvc.AuthorizeAttribute 的信息。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     filterContext 参数为 null。
        public virtual void OnAuthorization(AuthorizationContext filterContext);
        //
        // 摘要:
        //     重写时,提供一个入口点用于进行自定义授权检查。
        //
        // 参数:
        //   httpContext:
        //     HTTP 上下文,它封装有关单个 HTTP 请求的所有 HTTP 特定的信息。
        //
        // 返回结果:
        //     如果用户已经过授权,则为 true;否则为 false。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     httpContext 参数为 null。
        protected virtual bool AuthorizeCore(HttpContextBase httpContext);
        //
        // 摘要:
        //     处理未能授权的 HTTP 请求。
        //
        // 参数:
        //   filterContext:
        //     封装有关使用 System.Web.Mvc.AuthorizeAttribute 的信息。filterContext 对象包括控制器、HTTP 上下文、请求上下文、操作结果和路由数据。
        protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext);
        //
        // 摘要:
        //     在缓存模块请求授权时调用。
        //
        // 参数:
        //   httpContext:
        //     HTTP 上下文,它封装有关单个 HTTP 请求的所有 HTTP 特定的信息。
        //
        // 返回结果:
        //     对验证状态的引用。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     httpContext 参数为 null。
        protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext);
    }
}

可以看出Authorize是应用于类或者方法的特性,AuthorizeAttribute实现了IAuthorizationFilter接口和FilterAttribute抽象类,接口中的OnAuthorization(AuthorizationContext filterContext)方法是最终验证授权的逻辑(其中AuthorizationContext是继承了ControllerContext类),AuthorizeCore方法是最终OnAuthorization()方法调用的最终逻辑。

  • bool AuthorizeCore(HttpContextBase httpContext):授权验证的逻辑处理,返回true则是通过授权,返回false则不是。若验证不通过时,OnAuthorization方法内部会调用HandleUnauthorizedRequest
  • void HandleUnauthorizedRequest(AuthorizationContext filterContext):这个方法是处理授权失败的事情。

我们看下AuthorizeCore核心代码如下:

    protected virtual bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }
 
            IPrincipal user = httpContext.User;
            if (!user.Identity.IsAuthenticated)
            {
                return false;
            }
 
            if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
            {
                return false;
            }
 
            if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
            {
                return false;
            }
 
            return true;
        }

AuthorizeAttribute提供了四个虚方法,我们可以不使用默认的认证逻辑,可以根据自己的项目情况进行重写。想了解更多的过滤器特性可以看另一篇文章:MVC过滤器特性

现在了解了Authorize的原理,那么我们希望认证失败可以弹出登录(认证)页面,而不是401页面。下面先创建登录相关的页面。

4、新建LoginController

新建LoginController截图如下:

Form表单认证

LoginController中代码如下:

  • 检查用户提交的登录名和密码是否正确。

  • 根据登录名创建一个FormsAuthenticationTicket对象。

  • 调用FormsAuthentication.Encrypt()加密。

  • 根据加密结果创建登录Cookie,并写入Response。在登录验证结束后,一般会产生重定向操作, 那么后面的每次请求将带上前面产生的加密Cookie,供服务器来验证每次请求的登录状态。

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace FormAuthentication.Controllers
{
    public class LoginController : Controller
    {
        // GET: Login
        public ActionResult Index()
        {
            string returnUrl = Request["ReturnUrl"];
            if (Request.HttpMethod=="POST")
            {
                string userId = Request["userid"];
                string password = Request["password"];
                if (userId=="admin"&&password=="123")
                {
                    var ticket = new FormsAuthenticationTicket (
                        1,//version
                        userId,//name
                        DateTime.Now,//issueDate
                        DateTime.Now.AddMinutes(5),//expiration
                        true,//isPersistent 持久性保存在cookie中
                        "role1,role2,role3,role4",//userData 用户数据
                        "/"//cookiePath
                        );
                    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
                    cookie.HttpOnly = true;
                    HttpContext.Response.Cookies.Add(cookie);
                    return Redirect(returnUrl);
                }
            }
            return View();
        }
    }
}

5、新建LoginController/Index视图

新建LoginController/Index视图,截图如下:

Form表单认证

代码如下:

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>
<form method="post">
    <input type="text" name="userid" />
    <input type="password" name="password" />
    <input type="submit" value="认证" />
</form>

 

6、重定向登录页面 

上边通过4/5步完成了登录相关的页面,那么请求home/index如何重定向到登录页面呢?这一步可以通过配置文件进行处理,在<system.web>节点中加入如下信息:

<authentication mode="Forms">
    <forms loginUrl="~/Login/Index" timeout="2880"/>
</authentication>

Form表单认证

 

我们在执行下项目,看下效果:

Form表单认证

 

可以看到,请求home/index的时候,认证失败,会重定向login/index页面,我们输入admin和123,点击认证:

Form表单认证

 

认证成功后,生成一个加密的ticket放到cookie中,并且重定向原来的地址home/index:

Form表单认证

 

 可以看到认证成功了,刷新页面,再次请求home/index,请求头中会携带cookie中的ticket票据,当票据没有过期或者被删除的情况下,就不需要再次认证:

Form表单认证

 

上一篇:elementUI table 表格序号index 翻页 增加


下一篇:C# 常用公共方法