栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > asp

ASP.NET Forms身份认证详解

asp 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ASP.NET Forms身份认证详解

ASP.NET身份认证基础

在开始今天的内容之前,我想有二个最基础的问题首先要明确:

1. 如何判断当前请求是一个已登录用户发起的?

2. 如何获取当前登录用户的登录名?

在标准的ASP.NET身份认证方式中,上面二个问题的答案是:

1. 如果Request.IsAuthenticated为true,则表示是一个已登录用户。

2. 如果是一个已登录用户,访问HttpContext.User.Identity.Name可获取登录名(都是实例属性)。

接下来,本文将会围绕上面二个问题展开,请继续阅读。

ASP.NET身份认证过程

在ASP.NET中,整个身份认证的过程其实可分为二个阶段:认证与授权。

1. 认证阶段:识别当前请求的用户是不是一个可识别(的已登录)用户。

2. 授权阶段:是否允许当前请求访问指定的资源。

这二个阶段在ASP.NET管线中用AuthenticateRequest和AuthorizeRequest事件来表示。

在认证阶段,ASP.NET会检查当前请求,根据web.config设置的认证方式,尝试构造HttpContext.User对象供我们在后续的处理中使用。在授权阶段,会检查当前请求所访问的资源是否允许访问,因为有些受保护的页面资源可能要求特定的用户或者用户组才能访问。所以,即使是一个已登录用户,也有可能会不能访问某些页面。当发现用户不能访问某个页面资源时,ASP.NET会将请求重定向到登录页面。

受保护的页面与登录页面我们都可以在web.config中指定,具体方法可参考后文。

在ASP.NET中,Forms认证是由FormsAuthenticationModule实现的,URL的授权检查是由UrlAuthorizationModule实现的。

如何实现登录与注销

前面我介绍了可以使用Request.IsAuthenticated来判断当前用户是不是一个已登录用户,那么这一过程又是如何实现的呢?

为了回答这个问题,我准备了一个简单的示例页面,代码如下:

用户状态

页面显示效果如下:

根据前面的代码,我想现在能看到这个页面显示也是正确的,是的,我目前还没有登录(根本还没有实现这个功能)。

下面我再加点代码来实现用户登录。页面代码:

普通登录

现在页面的显示效果:

登录与退出登录的实现代码:

public void Logon() 
{ 
 FormsAuthentication.SignOut(); 
} 
 
public void NormalLogin() 
{ 
 // ----------------------------------------------------------------- 
 // 注意:演示代码为了简单,这里不检查用户名与密码是否正确。 
 // ----------------------------------------------------------------- 
 
 string loginName = Request.Form["loginName"]; 
 if( string.IsNullOrEmpty(loginName) ) 
  return; 
  
 FormsAuthentication.SetAuthcookie(loginName, true); 
 
 TryRedirect(); 
} 

现在,我可试一下登录功能。点击登录按钮后,页面的显示效果如下:

从图片的显示可以看出,我前面写的NormalLogin()方法确实可以实现用户登录。
当然了,我也可以在此时点击退出按钮,那么就回到了图片2的显示。

写到这里,我想有必要再来总结一下在ASP.NET中实现登录与注销的方法:

1. 登录:调用FormsAuthentication.SetAuthcookie()方法,传递一个登录名即可。

2. 注销:调用FormsAuthentication.SignOut()方法。

保护受限制的页面

在一个ASP.NET网站中,有些页面会允许所有用户访问,包括一些未登录用户,但有些页面则必须是已登录用户才能访问,还有一些页面可能会要求特定的用户或者用户组的成员才能访问。这类页面因此也可称为【受限页面】,它们一般代表着比较重要的页面,包含一些重要的操作或功能。

为了保护受限制的页面的访问,ASP.NET提供了一种简单的方式:可以在web.config中指定受限资源允许哪些用户或者用户组(角色)的访问,也可以设置为禁止访问。

比如,网站有一个页面:MyInfo.aspx,它要求访问这个页面的访问者必须是一个已登录用户,那么可以在web.config中这样配置:

 
  
   
    
   
  
 

为了方便,我可能会将一些管理相关的多个页面放在Admin目录中,显然这些页面只允许Admin用户组的成员才可以访问。对于这种情况,我们可以直接针对一个目录设置访问规则:

 
  
   
    
    
   
  
 

这样就不必一个一个页面单独设置了,还可以在目录中创建一个web.config来指定目录的访问规则,请参考后面的示例。

在前面的示例中,有一点要特别注意的是:

1. allow和deny之间的顺序一定不能写错了,UrlAuthorizationModule将按这个顺序依次判断。

2. 如果某个资源只允许某类用户访问,那么最后的一条规则一定是

在allow和deny的配置中,我们可以在一条规则中指定多个用户:

1. 使用users属性,值为逗号分隔的用户名列表。

2. 使用roles属性,值为逗号分隔的角色列表。

3. 问号 (?) 表示匿名用户。

4. 星号 (*) 表示所有用户。

登录页不能正常显示的问题

有时候,我们可能要开发一个内部使用的网站程序,这类网站程序要求 禁止匿名用户的访问,即:所有使用者必须先登录才能访问。因此,我们通常会在网站根目录下的web.config中这样设置:

 
  
 

对于我们的示例,我们也可以这样设置。此时在浏览器打开页面时,呈现效果如下:

从图片中可以看出:页面的样式显示不正确,最下边还多出了一行文字。

这个页面的完整代码是这样的(它引用了一个CSS文件和一个JS文件):

<%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default" %> 
 
 
 FormsAuthentication DEMO - http://www.cnblogs.com/fish-li/ 
  
 
 
 
普通登录
用户状态

不应该显示的文字

页面最后一行文字平时不显示是因为Jscript.js中有以下代码:

document.getElementById("hideText").setAttribute("style", "display: none"); 

这段JS代码能做什么,我想就不用再解释了。虽然这段JS代码没什么价值,但我主要是想演示在登录页面中引用JS的场景。

根据前面图片,我们可以猜测到:应该是CSS和JS文件没有正确加载造成的。为了确认就是这样原因,我们可以打开FireBug再来看一下页面加载情况:

根据FireBug提供的线索我们可以分析出,页面在访问CSS, JS文件时,其实是被重定向到登录页面了,因此获得的结果肯定也是无意义的,所以就造成了登录页的显示不正确。

还记得【授权】吗?
是的,现在就是由于我们在web.config中设置了不允许匿名用户访问,因此,所有的资源也就不允许匿名用户访问了,包括登录页所引用的CSS, JS文件。当授权检查失败时,请求会被重定向到登录页面,所以,登录页本身所引用的CSS, JS文件最后得到的响应内容其实是登录页的HTML代码,最终导致它们不能发挥作用,表现为登录页的样式显示不正确,以及引用的JS文件也不起作用。

不过,有一点比较奇怪:为什么访问登录页面时,没有发生重定向呢?

原因是这样的:在ASP.NET内部,当发现是在访问登录面时,会设置HttpContext.SkipAuthorization = true (其实是一个内部调用),这样的设置会告诉后面的授权检查模块:跳过这次请求的授权检查。 因此,登录页总是允许所有用户访问,但是CSS文件以及JS文件是在另外的请求中发生的,那些请求并不会要跳过授权模块的检查。

为了解决登录页不能正确显示的问题,我们可以这样处理:

1. 在网站根目录中的web.config中设置登录页所引用的JS, CSS文件都允许匿名访问。

2. 也可以直接针对JS, CSS目录设置为允许匿名用户访问。

3. 还可以在CSS, JS目录中创建一个web.config文件来配置对应目录的授权规则。可参考以下web.config文件:

 
 
  
   
    
   
  
 

第三种做法可以不修改网站根目录下的web.config文件。

注意:在IIS中看到的情况就和在Visual Studio中看到的结果就不一样了。 因为,像js, css, image这类文件属于静态资源文件,IIS能直接处理,不需要交给ASP.NET来响应,因此就不会发生授权检查失败,所以,如果这类网站部署在IIS中,看到的结果又是正常的。

认识Forms身份认证

前面我演示了如何用代码实现登录与注销的过程,下面再来看一下登录时,ASP.NET到底做了些什么事情,它是如何知道当前请求是一个已登录用户的?

在继续探索这个问题前,我想有必要来了解一下HTTP协议的一些特点。

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

虽然HTTP协议与WEB服务器是无状态,但我们的业务需求却要求有状态,典型的就是用户登录,在这种业务需求中,要求WEB服务器端能区分某个请求是不是一个已登录用户发起的,或者当前请求是哪个用户发出的。在开发WEB应用程序时,我们通常会使用cookie来保存一些简单的数据供服务端维持必要的状态。既然这是个通常的做法,那我们现在就来看一下现在页面的cookie使用情况吧,以下是我用FireFox所看到的cookie列表:

这个名字:LogincookieName,是我在web.config中指定的:

 
  

登录处理代码:

public void CustomizeLogin() 
{ 
 // ----------------------------------------------------------------- 
 // 注意:演示代码为了简单,这里不检查用户名与密码是否正确。 
 // ----------------------------------------------------------------- 
 
 string loginName = Request.Form["loginName"]; 
 if( string.IsNullOrEmpty(loginName) ) 
  return; 
 
 
 UserInfo userinfo = new UserInfo(); 
 int.TryParse(Request.Form["UserId"], out userinfo.UserId); 
 int.TryParse(Request.Form["GroupId"], out userinfo.GroupId); 
 userinfo.UserName = Request.Form["UserName"]; 
 
 // 登录状态100分钟内有效 
 MyFormsPrincipal.SignIn(loginName, userinfo, 100); 
 
 TryRedirect(); 
} 

显示用户信息的页面代码:

用户状态

为了能让上面的页面代码发挥工作,必须在页面显示前重新设置HttpContext.User对象。
为此,我在Global.asax中添加了一个事件处理器:

protected void Application_AuthenticateRequest(object sender, EventArgs e) 
{ 
 HttpApplication app = (HttpApplication)sender; 
 MyFormsPrincipal.TrySetUserInfo(app.Context); 
} 
TrySetUserInfo的实现代码:

///  
/// 根据HttpContext对象设置用户标识对象 
///  
///  
public static void TrySetUserInfo(HttpContext context) 
{ 
 if( context == null ) 
  throw new ArgumentNullException("context"); 
 
 // 1. 读登录cookie 
 Httpcookie cookie = context.Request.cookies[FormsAuthentication.FormscookieName]; 
 if( cookie == null || string.IsNullOrEmpty(cookie.Value) ) 
  return; 
  
 try { 
  TUserData userData = null; 
  // 2. 解密cookie值,获取FormsAuthenticationTicket对象 
  FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value); 
 
  if( ticket != null && string.IsNullOrEmpty(ticket.UserData) == false ) 
   // 3. 还原用户数据 
   userData = (new JavascriptSerializer()).Deserialize(ticket.UserData); 
 
  if( ticket != null && userData != null ) 
   // 4. 构造我们的MyFormsPrincipal实例,重新给context.User赋值。 
   context.User = new MyFormsPrincipal(ticket, userData); 
 } 
 catch {  } 
} 

在多台服务器之间使用Forms身份认证

默认情况下,ASP.NET 生成随机密钥并将其存储在本地安全机构 (LSA) 中,因此,当需要在多台机器之间使用Forms身份认证时,就不能再使用随机生成密钥的方式, 需要我们手工指定,保证每台机器的密钥是一致的。

用于Forms身份认证的密钥可以在web.config的machineKey配置节中指定,我们还可以指定加密解密算法:

 

关于这二个属性,MSDN有如下解释:

 

在客户端程序中访问受限页面
这一小节送给所有对自动化测试感兴趣的朋友。

有时我们需要用代码访问某些页面,比如:希望用代码测试服务端的响应。

如果是简单的页面,或者页面允许所有客户端访问,这样不会有问题,但是,如果此时我们要访问的页面是一个受限页面,那么就必须也要像人工操作那样:先访问登录页面,提交登录数据,获取服务端生成的登录cookie,接下来才能去访问其它的受限页面(但要带上登录cookie)。

注意:由于登录cookie通常是加密的,且会发生变化,因此直接在代码中硬编码指定登录cookie会导致代码难以维护。

在前面的示例中,我已在web.config为MyInfo.aspx设置过禁止匿名访问,如果我用下面的代码去调用:

private static readonly string MyInfoPageUrl = "http://localhost:51855/MyInfo.aspx"; 
 
static void Main(string[] args) 
{ 
 // 这个调用得到的结果其实是default.aspx页面的输出,并非MyInfo.aspx 
 HttpWebRequest request = MyHttpClient.CreateHttpWebRequest(MyInfoPageUrl); 
 string html = MyHttpClient.GetResponseText(request); 
 
 if( html.IndexOf("Fish") > 0 ) 
  Console.WriteLine("调用成功。"); 
 else 
  Console.WriteLine("页面结果不符合预期。"); 
} 

此时,输出的结果将会是:

页面结果不符合预期。

如果我用下面的代码:

private static readonly string LoginUrl = "http://localhost:51855/default.aspx"; 
private static readonly string MyInfoPageUrl = "http://localhost:51855/MyInfo.aspx"; 
 
static void Main(string[] args) 
{ 
 // 创建一个cookieContainer实例,供多次请求之间共享cookie 
 cookieContainer cookieContainer = new cookieContainer(); 
 
 // 首先去登录页面登录 
 MyHttpClient.HttpPost(LoginUrl, "NormalLogin=aa&loginName=Fish", cookieContainer); 
 
 // 此时cookieContainer已经包含了服务端生成的登录cookie 
 
 // 再去访问要请求的页面。 
 string html = MyHttpClient.HttpGet(MyInfoPageUrl, cookieContainer); 
 
 if( html.IndexOf("Fish") > 0 ) 
  Console.WriteLine("调用成功。"); 
 else 
  Console.WriteLine("页面结果不符合预期。"); 
 
 // 如果还要访问其它的受限页面,可以继续调用。 
} 

此时,输出的结果将会是:

调用成功。

说明:在改进的版本中,我首先创建一个cookieContainer实例,它可以在HTTP调用过程中接收服务器产生的cookie,并能在发送HTTP请求时将已经保存的cookie再发送给服务端。在创建好cookieContainer实例之后,每次使用HttpWebRequest对象时,只要将cookieContainer实例赋值给HttpWebRequest对象的cookieContainer属性,即可实现在多次的HTTP调用中cookie的接收与发送,最终可以模拟浏览器的cookie处理行为,服务端也能正确识别客户的身份。

ASP.NET Forms身份认证就说到这里,如果您对ASP.NET Windows身份认证有兴趣,那么请继续关注相关文章。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/56686.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号