现在的位置: 首页 > 综合 > 正文

asp.net Forms表单验证 使用经验及验证流程分析

2013年11月11日 ⁄ 综合 ⁄ 共 13127字 ⁄ 字号 评论关闭

      最近,要做一个登陆的页面,就想到了安全性方面的问题。记得曾经在邵志东老师讲的关于asp.net安全性方面的课程中,提到asp.net提供了4个身份验证程序:1.表单身份验证;2.Windows身份验证;3.Passport身份验证;4.默认身份验证。尤其讲了表单身份验证,想想,正好自己以前也不曾使用过这个验证方式,那就拿来练练手吧。

      表单验证,可以根据用户和角色来限制用户访问。比如,我们有以一个后台管理系统,只有通过后台登陆页面合法登陆的用户才能访问后台管理系统中的任何页面,这个时候我们就可以通过表单验证来实现(过去我都是在每一个页面写判断逻辑。现在想起来,过去的那种方法真是不折不扣的体力劳动,而且如果哪个页面忘记写了,就麻烦了)。

      实验开始(因为只记录经验,所以有些知识点这里并没有提到,需要大家多花点课外时间了。文末提供了些链接供大家参考)

      我接下来就来做一个Forms表单验证的例子。在该例子中,我建立了两个文件夹分别为User和Admin,在每一个文件夹中又有login.aspx、index.aspx和web.config。我希望普通用户访问User文件夹需要首先要在用户登陆界面进行登陆,成功后才能访问用户的index.aspx。而管理员则首先要在Admin的登陆界面进行登陆,才能访问Admin中的index.aspx。而在网站根目录有LoginRedirect.aspx、web.config和Global.asax。
      目录结构图:

      

      如何才能实现表单验证呢?
      1.配置根目录下的web.config (在网站根目录下web.config文件中的system.web标记中,修改原<authentication mode="Windows" />为如下代码)


        <authentication mode="Forms">
            
<forms name="adminlogin" loginUrl="loginRedirect.aspx">
            
</forms>
        
</authentication>
        
<authorization>
            
<allow users="*"/>
        
</authorization>

      上述的配置是什么意思呢?
      首先,这里有两个不同的配置节,authentication和authorization看上去是不是很像? 你可千万不要被眼睛欺骗了,这两个是不同的意思,前者是“验证”,后者是“授权”。
      接着,我们来看下“验证”这个配置节中的东西。
      mode表示的就是验证方式,这里有四个选项:Windows、Forms、Passport、None。默认的是Windows。我们这里选择Forms。
      而在forms元素里,设置了name和loginUrl。
      name表示cookie的名字,我们后面要通过cookie来保存一些用户信息并将包含cookie信息的http请求发送到服务器。服务器端,则会根据cookie信息对用户进行标识,从而进行进一步的验证。
      LoginUrl 顾名思义就是登陆页面的地址。如果说用户没有权限访问某一页面,就会被重定向到这个页面。

      还有其它诸多元素,请大家自己查找相关资料。我也会在文末给出一些我认为比较不错的链接。

      讲完了“验证”节,接着讲讲“授权”节。
      授权,自然是要限制哪些用户或角色可以访问,哪些用户或角色不能访问了。设置的方式就是通过设置<allow>和<deny>。如上所示的<allow users="*">就是表示允许所有用户访问。你可能会奇怪不是要限制用户访问吗,怎么全部允许了?那是因为,我就是希望“根目录下”的所有东西都可以被任何用户访问。

      再来看看两个子文件夹内的web.config。


<configuration>
  
<location path="login.aspx">
    
<system.web>
      
<authorization>
        <allow users="*"/>
      
</authorization>
    
</system.web>
  
</location>
  
<system.web>
    
<authorization>
      
<allow roles="user"/>
      
<deny users="*"/>
    
</authorization>
  
</system.web>
</configuration>

      在这个配置文件中,不能配置“验证”节的内容(该内容只能在虚拟目录的根目录web.config中配置),我们只能对“授权”节进行配置。上述的location的作用是表示该路径不需要进行授权检查,因为我希望任何用户都可以进到登陆页面,原因大家应该都想得到吧。
      而其他路径,则不希望未登陆的用户或网站管理员登陆,因此使用<allow roles="user">来允许只有角色为user的用户访问,而其他任何用户都拒绝访问。
      同理,来看下管理员目录的web.config,请大家自己分析。


<configuration>
  
<location path="login.aspx">
    
<system.web>
      
<authorization>
        
<allow users="*"/>
      
</authorization>
    
</system.web>
  
</location>
  
<system.web>
    
<authorization>
      
<allow roles="Manager"/>
      
<deny users="*"/>
    
</authorization>
  
</system.web>
</configuration>

      配置好了之后,我们还需要写一些cs代码。首先我们来看一下loginRedirect.aspx.cs。因为,我们现在访问上述任何一个子文件的页面时,都会先跳转到这个页面里来,而其实我则希望可以跳到相信子目录的登陆页面中去。因此,需要在这个文件中进行一些判断。


string from = Request.QueryString["ReturnUrl"];//每个跳转过来的页面都会带有ReturnUrl值,以此来获取跳转之前的页面
//获取子目录名称
string fromFilePath = from.Substring(from.IndexOf('/'+ 1, from.IndexOf('/', from.IndexOf('/'+ 1- from.IndexOf('/')-1);
//根据子目录名称来判断跳转的链接
switch (fromFilePath.ToLower())
{
    
case "admin": Response.Redirect("/admin/login.aspx"); break;
    
case "user": Response.Redirect("/user/login.aspx"); break;
}

       有些人可能奇怪了,这么麻烦,既然可以在“验证”节中配置loginUrl,难道就不能对每个目录实现直接跳转到本目录相应登陆页面吗?很遗憾,目前为止,我还没有找到直接的解决办法。如果您有什么办法,请不吝赐教。
       跳转到登陆页面后,那我们就应该对用户的登陆时间进行处理了。


protected void Page_Load(object sender, EventArgs e)
{
    
//判断用户是否已经登陆,且角色为user
    if (User.Identity.IsAuthenticated&&User.IsInRole("user"))
    {
//如果通过验证,则直接跳转到index.aspx
        Response.Redirect("index.aspx");
    }
}

//登陆按钮事件,这里简单起见,我直接以用户名"user",密码"1"来判断,当然你也可以从数据库读取。
protected void btnLogin_Click(object sender, EventArgs e)
{
    
if (tbUserName.Text == "user" && tbPwd.Text == "1")
    {
        
//生成验证票据,其中包括用户名、生效时间、过期时间、是否永久保存和用户数据等。而关于用户角色的信息,我们保存在用户数据中。
        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, tbUserName.Text, DateTime.Now, DateTime.Now.AddMinutes(30), true"User");
        
string cookieStr = FormsAuthentication.Encrypt(ticket);//对票据进行加密
        HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieStr);
/*保存到cookie中。cookie的名字要与我们前面在配置文件中所写的name值一样。因为,当cookie保留在本地后,下次再检查用户权限的时候就会自动查找与forms名称相同的cookie,并传送给服务器端进行检验。如果在本地找不到cookie,就自然无法通过验证。*/
        cookie.Expires 
= ticket.Expiration;
        cookie.Path 
= FormsAuthentication.FormsCookiePath;
        Response.Cookies.Add(cookie);
        Response.Redirect(
"index.aspx");//登陆成功后跳转到index.aspx
    }
}
/*
这里突然冒出一个票据,有些朋友是不是很奇怪呀?票据什么用呢?
票据其实也可以理解为凭据(只有有凭据的用户才能通过检查),它包括了上面注释中所写的一些与用户相关的信息。但是票据不能直接传送给服务器必须通过cookie来承载。而服务器端在接受到cookie之后,会从中取出票据的数据,并进行相关操作。
*/

      在Admin文件夹下的login.aspx.cs也是类似。就不再赘述了。
      差点忘了最后的一个东西了,就是为我们的用户配置角色。上面我们在创建票据的时候发现没有提供直接对角色赋值的功能,那我们就只能利用grobal.asax来实现了。
      在global.asax中有一个Application_AuthenticateRequest事件,该事件会在服务器决定该用户浏览器是否应该跳转前发生。因此,我们只要在这里对用户角色进行配置,就可以达到目的。


protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    
if (HttpContext.Current.User != null)
    {
//如果用户通过验证,则该项不为null
        if (HttpContext.Current.User.Identity.IsAuthenticated)
        {
            
if (HttpContext.Current.User.Identity is FormsIdentity)
            {
                FormsIdentity id 
= (FormsIdentity)HttpContext.Current.User.Identity;
                FormsAuthenticationTicket ticket 
= id.Ticket;

                string userData = ticket.UserData;//取出角色数据
                string[] roles = userData.Split(',');
                HttpContext.Current.User 
= new GenericPrincipal(id, roles);//重新分配角色
            }
        }
    }
}

      大家可以下载整个工程来看。
      【注意:不能在同一台电脑上即登陆用户界面又登陆管理员界面。因为使用Forms名称所能保留的cookie只可能是一个,所以如果登陆了管理员界面后,接着登陆用户界面,就会覆盖原来的cookie值。有些朋友使用session来保存用户数据的方法,为实验。】
      实验结束

      Forms验证流程
      大致页面处理流程如下:(介绍这个是为了引出FormsAuthenticationModule,因此不详细介绍,大家可以参考:ASP.NET页面生成流程
      客户端的http请求到达服务器端后,首先IIS会接收到该消息,然后调用asp.net isapi扩展,并由该扩展将请求等信息传送给.net运行时。.Net Runtime会调用ProcessRequest方法根据客户端发来的请求信息创建并初始化一个HttpWorkerRequest对象,并根据该对象创建HttpContext对象,该对象中包含HttpRequest、HttpResponse对象,其中HttpRequest对象中又包含cookie和浏览器信息。最终会生成并调用HttpApplication对象的Init方法。在Init方法中会调用HttpApplicaion.InitInternal方法。而该方法又会调用InitModules方法。InitModules方法则会初始化Web.Config中注册的所有模块(HttpModule)。

      我们的主角FormsAuthenticationModule也就是在这里上场了。
      初始化FormsAuthenticationModule的过程中,会先调用该Module的init方法。我们来看一下该方法中有些什么:

public void Init(HttpApplication app)
{
    app.AuthenticateRequest 
+= new EventHandler(this.OnEnter);
    app.EndRequest 
+= new EventHandler(this.OnLeave);
}

      Init方法注册了两个事件:OnEnter和OnLeave。分别在HttpApplication.AuthenticateRequest和EndRequst事件被触发时执行。
      这两个事件具体做了些什么呢? 

OnEnter

      在OnEnter事件中,我们提到一个重要的方法就是OnAuthenticate:

OnAuthenticate

      在执行了这个module之后,还有一个重要的module我们不得不提的就是UrlAuthorizationModule。在这个module中,对上面所设置的用户进行了授权检查,来确定该用户是否可以访问所请求的页面。如果用户没有权限,则跳转到loginUrl中所指定的页面。主要方法就是OnEnter:

OnEnter

抱歉!评论已关闭.