一、Form表单验证

  1、基本概念

  表单验证是一个基于票据(ticket-based)[也称为基于令牌(token-based)]的系统。当用户登录系统以后,会得到一个包含基于用户信息的票据(ticket)。这些信息被存放在加密过的cookie里面,这些cookie和响应绑定在一起,因此每一次后续请求都会被自动提交到服务器。

  表单验证流程:

  (1)、用户请求一个需要验证身份后才可以访问的ASP.NET页面时,ASP.NET运行时验证这个表单验证票据是否有效。如果无效,ASP.NET自动将用户转到登录页面。

  (2)、登录页面验证提交的凭证。如果用户验证成功,你只需要告诉ASP.NET架构验证成功(通过调用FormsAuthentication类的一个方法),运行库会自动设置验证cookie(实际上包含了票据)并将用户转到原先请求的页面。

  (3)、通过这个请求,运行库检测到验证cookie中包含一个有效票据,然后赋给用户对这个页面的访问权限。

  参考以下流程图:

  

  需要的开发工作:

  (1)、在web.config配置为表单验证   

    <authentication mode="Forms">
      <forms loginUrl="/Home/Login"></forms>
    </authentication>

  (2)、创建登录页面

  (3)、登录成功后创建凭证(进行Form验证)

    方式1:

      FormsAuthentication.SetAuthCookie(userName, false);  设置Cookie,其中false,cookie不是永久的,反之,是永久的

    方式2:   

      // 创建ticket凭据
      FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(userName, false,1);
      //加密ticket凭据
      var encryptTicket = FormsAuthentication.Encrypt(ticket);
      //写入cookie
      Response.Cookies.Add(new HttpCookie(FormsAuthentication.FromCookieName, encryptTicket));

  

  2、表示用户身份的两个对象 IPrincipal IIdentity

 public interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string role);
}
  public interface IIdentity
{
string Name { get; }
string AuthenticationType { get; }
bool IsAuthenticated { get; }
}

  (1)、安全上下文 = 安全信息 = HttpContext.User IPrincipal

  (2)、身份标识 = 用户标识 = HttpContext.User.Identity = IIdentity

  (3)、安全上下文 = 身份标识 + 角色信息 + 其他数据

  请求经过身份验证的阶段后,或者说AuthenticateRequest事件开始后,才能通过HttpContext.User.Identity获取到具体的身份标识信息,包括用户名(Name),是否已经登录(IsAuthenticated)等属性值,即在HttpApplication.AuthenticateRequest事件中,在此事件中ASP.NET会构建(创建/更新)安全上下文对象(继承IPrincipal接口)和身份标识对象(继承IIdentity接口);用户未登录也会生成一个默认的身份标识,通常称为匿名用户,页面如果不允许匿名用户访问就要跳转到登录页面。

  表单验证,验证通过后的身份标识类是FormsIdentity

  两个需要注意的地方:

  (1)、在更早的BeginRequest事件里是完全获取不到用户的身份信息的,毕竟那个时候User.Identity都还没有进行构建。

  (2)、在身份验证事件(AuthenticateRequest)过后,HttpContext.User.Identity对象的属性值基本上不会进行改变。除非通过代码重新设置HttpContext.User这个安全上下文对象。

  3、FormsAuthentication.SignOut()

  只是让包含用户信息票据的Cookie过期,删除表单身份验证的cookie,并没有改变当前用户的身份标识信息,也就是说HttpContext.User.Identity对象是没有任何变化的,IsAuthenticated的值也没有变,一般的账户注销流程里,注销操作的后续都是紧跟着重定向操作,后续发起的新请求才能重新进行身份验证。

  一般我们注销的代码有以下两种:

  (1)、直接重定向

[HttpPost]
public ActionResult SignOut()
{ FormsAuthentication.SignOut();
//方法一
return Redirect("/Login");
//方法二,需要在配置文件设置默认登录页面
//FormsAuthentication.RedirectToLoginPage();
}

  (2)、重新设置HttpContext.User对象

    赋值一个空白的用户名从而手动删除掉身份标识,因为User是只读的,所以只能覆盖

[HttpPost]
public ActionResult SignOut()
{
FormsAuthentication.SignOut();
HttpContext.User = new System.Security.Principal.GenericPrincipal(new System.Security.Principal.GenericIdentity(string.Empty), null);
return View();
}

  4、账户登录后无法获取用户名问题

  登录后,通过FormsAuthentication.SetAuthCookie方法或者通过代码将FormsAuthenticationTicket对象添加到响应的Cookie里,都是无法马上获取用户名的,User.Identity.Name属性的值还是为""。这两个方法都只是对包含身份验证票据的Cookie进行操作,用户的身份标识都还没有更新。

  解决的方法

  (1)、在代码中手动设置HttpContext.User

  (2)、做个重定向跳转,而且按照一般的用户账户登录流程,在账户登录后要么跳转到原前请求的URL,要么跳转到某个默认页面。所以一般很少发生这种问题。

二、Authorize特性

  1、源码分析

  核心代码是AuthorizeCore方法中的user.Identity.IsAuthenticated,Form表单验证通过后就为true,就是上节表单验证后,设置Cookie后。

 public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
private static readonly char[] _splitParameter = new[] { ',' };
private readonly object _typeId = new object(); private string _roles;
private string[] _rolesSplit = new string[];
private string _users;
private string[] _usersSplit = new string[]; public string Roles
{
get { return _roles ?? String.Empty; }
set
{
_roles = value;
_rolesSplit = SplitString(value);
}
} public override object TypeId
{
get { return _typeId; }
} public string Users
{
get { return _users ?? String.Empty; }
set
{
_users = value;
_usersSplit = SplitString(value);
}
} // 核心方法
protected virtual bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
//主要验证user.Identity.IsAuthenticated
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated)
{
return false;
} if (_usersSplit.Length > && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
{
return false;
} if (_rolesSplit.Length > && !_rolesSplit.Any(user.IsInRole))
{
return false;
} return true;
} private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
} //主要IAuthorizationFilter接口方法
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
} if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
{ throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
} bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true); if (skipAuthorization)
{
return;
}
//调用AuthorizeCore
if (AuthorizeCore(filterContext.HttpContext))
{ HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan());
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
HandleUnauthorizedRequest(filterContext);
}
} protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new HttpUnauthorizedResult();
} protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
//调用AuthorizeCore
bool isAuthorized = AuthorizeCore(httpContext);
return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
} internal static string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[];
} var split = from piece in original.Split(_splitParameter)
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}

  基本使用:

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

  2、集群环境下的单点登录

  在多个WEB服务器情况下,登录后,把cookie数据可以放在统一的Redis或Mongodb服务器上,把sessionId当键值。在验证时候,自定义一个Authorize特性,重载

AuthorizeCore方法,根据sessionId从Redis或Mongodb服务器上获取cookie数据,来验证是否登录。

  3、表单认证过程详解

  Forms认证的流程设计4次的消息交换,其具体步骤如下所示。

    步骤1:用户通过浏览器匿名向IIS发起请求,假设地址为"/home",它会收到状态为"302, Found"的相应,这是一个用于实现"重定向"的http响应,它通过location报头表示的重定向地址指向登录的页面,之前访问的地址将作为查询字符串returnURL的值。

    步骤2:浏览器接受到该请求后,针对重定向的地址发送请求,登录页面最终被呈现在浏览器。

    步骤3:用户输入正确的用户名密码后提交表单,服务器在接受到请求之后提取它们对用户实施认证,认证成功后,它会生成一个安全令牌或者认证票据。接下来,通过查询字符串returnURL表示的原始请求地址,作为另一个状态为"302, Found"响应的Location报头,而经过加密/签名的安全令牌作为该响应的Cookie

    步骤4:这个代表安全令牌的Cookie将自动附加到浏览器后续的请求中,服务器直接利用它对请求实施认证。Cookie的名称、过期策略以及采用的保护等级均可以通过配置来控制。在禁用Cookie的情况下,安全令牌会直接作为URL的一部分传送。

  

ASP.NET MVC Form表单验证与Authorize特性的更多相关文章

  1. python26:自定义form表单验证

    一.自定义Form的原理 1.1 各种form表单验证比较 只有python提供了form表单验证,其他的都没有提供.django提供的功能还不够强大.最强大的是微软的ASP.NET!我们可以自己写一 ...

  2. form表单验证-Javascript

    Form表单验证: js基础考试内容,form表单验证,正则表达式,blur事件,自动获取数组,以及css布局样式,动态清除等.完整代码如下: <!DOCTYPE html PUBLIC &qu ...

  3. django之form表单验证

    django中的Form一般有两种功能: 输入html 验证用户输入 #!/usr/bin/env python # -*- coding:utf- -*- import re from django ...

  4. spring mvc form表单提交乱码

    spring mvc form表单submit直接提交出现乱码.导致乱码一般是服务器端和页面之间编码不一致造成的.根据这一思路可以依次可以有以下方案. 1.jsp页面设置编码 <%@ page ...

  5. python_way day19 HTML-day5 (form表单验证,CSRF,cookie,session,缓存)

    python-way day19 1. dJango的form表单验证 2.CSRF 跨站请求伪造 3.cookie,session 4.缓存 一,django表单验证功能 1.django验证基础: ...

  6. form表单验证2

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. Day19 Django之Form表单验证、CSRF、Cookie、Session和Model操作

    一.Form表单验证 用于做用户提交数据的验证1.自定义规则 a.自定义规则(类,字段名==html中的name值)b.数据提交-规则进行匹配代码如下: """day19 ...

  8. [php基础]PHP Form表单验证:PHP form validator使用说明

    在PHP网站开发建设中,用户注册.留言是必不可少的功能,用户提交的信息数据都是通过Form表单提交,为了保证数据的完整性.安全性,PHP Form表单验证是过滤数据的首要环节,PHP对表单提交数据的验 ...

  9. django form表单验证

    一. django form表单验证引入 有时时候我们需要使用get,post,put等方式在前台HTML页面提交一些数据到后台处理例 ; <!DOCTYPE html> <html ...

随机推荐

  1. 错误:在非结构或联合中请求成员‘next’

    #include <stdio.h> #include <stdlib.h> #define ElemType int #define Status int #define O ...

  2. node.js 关于 async的使用

    第一次使用,感觉有点糊涂,后来实验明白了. 在串行执行中,经常会只做了第一步.后来明白了.是没有把回调函数放在里面简单就是:  async.series(                 {     ...

  3. 转:百度MySql5.7安装配置

    原文地址:http://jingyan.baidu.com/article/8cdccae946133f315513cd6a.html MySQL 5.7以上版本的配置和以前有所不同,在这里与大家分享 ...

  4. 伪静态的服务器配置-如何php为 Discuz! X2 配置伪静态

      URL 静态化是一个有利于搜索引擎的设置,通过 URL 静态化,达到原来是动态的 PHP 页面转换为静态化的 HTML 页面,可以提高搜索引擎抓取,当然,这里的静态化是一种假静态,目的只是提高搜索 ...

  5. 数组转xml格式/xml格式转数组

    数组转xml格式 $arr=array( 'username'=>'huahua', 'password'=>'123456', 'number'=>'15889652911', ) ...

  6. base64编码是什么1

    首先明确一点base64 是一种编码格式.就想UNICODE一样,能在电脑上表示所有字符,或者换句话说通过编码能让电脑理解你想要表示的字符(因为电脑只知道0和1 ) 就像ascII 中 0100 00 ...

  7. javase高级技术 - 反射

    在说反射之前,必须得先说说java的类加载器,类加载器的定义:将.class文件加载到内在中,并为之生成对应的Class对象. 一般有三种 1 Bootstrap ClassLoader 根类加载器也 ...

  8. 51nod1347 旋转字符串

    题目很容易懂,只要进行几次简单的判断就能完成此题,显示判断是否为偶数,之后利用sustr截取两个字符串进行比较,代码如下 #include<iostream> #include<st ...

  9. [C#.Net]对WinForm应用程序的App.config的使用及加密

    我们在写C#应用程序时,在工程文件中放置一个app.config,程序打包时,系统会将该配置文件自动编译为与程序集同名的.exe.config 文件.作用就是应用程序安装后,只需在安装目录中找到该文件 ...

  10. 买铅笔(NOIP2016)

    先给题目链接:买铅笔 这题非常水,没啥可分析的,先给代码: #include<bits/stdc++.h> //1 using namespace std; int main(){ int ...