时间:2021-05-28
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来判断当前用户是不是一个已登录用户,那么这一过程又是如何实现的呢?
为了回答这个问题,我准备了一个简单的示例页面,代码如下:
<fieldset><legend>用户状态</legend><form action="<%= Request.RawUrl %>" method="post"> <% if( Request.IsAuthenticated ) { %> 当前用户已登录,登录名:<%= Context.User.Identity.Name.HtmlEncode() %> <br /> <input type="submit" name="Logon" value="退出" /> <% } else { %> <b>当前用户还未登录。</b> <% } %> </form></fieldset>页面显示效果如下:
根据前面的代码,我想现在能看到这个页面显示也是正确的,是的,我目前还没有登录(根本还没有实现这个功能)。
下面我再加点代码来实现用户登录。页面代码:
<fieldset><legend>普通登录</legend><form action="<%= Request.RawUrl %>" method="post"> 登录名:<input type="text" name="loginName" style="width: 200px" value="Fish" /> <input type="submit" name="NormalLogin" value="登录" /> </form></fieldset>现在页面的显示效果:
登录与退出登录的实现代码:
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中这样配置:
<location path="MyInfo.aspx"> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </location>为了方便,我可能会将一些管理相关的多个页面放在Admin目录中,显然这些页面只允许Admin用户组的成员才可以访问。对于这种情况,我们可以直接针对一个目录设置访问规则:
<location path="Admin"> <system.web> <authorization> <allow roles="Admin"/> <deny users="*"/> </authorization> </system.web> </location>这样就不必一个一个页面单独设置了,还可以在目录中创建一个web.config来指定目录的访问规则,请参考后面的示例。
在前面的示例中,有一点要特别注意的是:
1. allow和deny之间的顺序一定不能写错了,UrlAuthorizationModule将按这个顺序依次判断。
2. 如果某个资源只允许某类用户访问,那么最后的一条规则一定是 <deny users="*" />
在allow和deny的配置中,我们可以在一条规则中指定多个用户:
1. 使用users属性,值为逗号分隔的用户名列表。
2. 使用roles属性,值为逗号分隔的角色列表。
3. 问号 (?) 表示匿名用户。
4. 星号 (*) 表示所有用户。
登录页不能正常显示的问题
有时候,我们可能要开发一个内部使用的网站程序,这类网站程序要求 禁止匿名用户的访问,即:所有使用者必须先登录才能访问。因此,我们通常会在网站根目录下的web.config中这样设置:
<authorization> <deny users="?"/> </authorization>对于我们的示例,我们也可以这样设置。此时在浏览器打开页面时,呈现效果如下:
从图片中可以看出:页面的样式显示不正确,最下边还多出了一行文字。
这个页面的完整代码是这样的(它引用了一个CSS文件和一个JS文件):
<%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="_Default" %> <html xmlns="http://pare(role, "Admin", true) == 0 ) return GroupId == 1; else return GroupId > 0; } #endregion }注意:表示用户信息的类型并不要求一定要实现IPrincipal接口,如果不需要用户组的判断,可以不实现这个接口。
登录时需要调用的方法(定义在MyFormsPrincipal类型中):
/// <summary> /// 执行用户登录操作 /// </summary> /// <param name="loginName">登录名</param> /// <param name="userData">与登录名相关的用户信息</param> /// <param name="expiration">登录Cookie的过期时间,单位:分钟。</param> public static void SignIn(string loginName, TUserData userData, int expiration) { if( string.IsNullOrEmpty(loginName) ) throw new ArgumentNullException("loginName"); if( userData == null ) throw new ArgumentNullException("userData"); // 1. 把需要保存的用户数据转成一个字符串。 string data = null; if( userData != null ) data = (new JavaScriptSerializer()).Serialize(userData); // 2. 创建一个FormsAuthenticationTicket,它包含登录名以及额外的用户数据。 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( 2, loginName, DateTime.Now, DateTime.Now.AddDays(1), true, data); // 3. 加密Ticket,变成一个加密的字符串。 string cookieValue = FormsAuthentication.Encrypt(ticket); // 4. 根据加密结果创建登录Cookie HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue); cookie.HttpOnly = true; cookie.Secure = FormsAuthentication.RequireSSL; cookie.Domain = FormsAuthentication.CookieDomain; cookie.Path = FormsAuthentication.FormsCookiePath; if( expiration > 0 ) cookie.Expires = DateTime.Now.AddMinutes(expiration); HttpContext context = HttpContext.Current; if( context == null ) throw new InvalidOperationException(); // 5. 写登录Cookie context.Response.Cookies.Remove(cookie.Name); context.Response.Cookies.Add(cookie); }这里有必要再补充一下:登录状态是有过期限制的。Cookie有 有效期,FormsAuthenticationTicket对象也有 有效期。这二者任何一个过期时,都将导致登录状态无效。按照默认设置,FormsAuthenticationModule将采用slidingExpiration=true的策略来处理FormsAuthenticationTicket过期问题。
登录页面代码:
<fieldset><legend>包含【用户信息】的自定义登录</legend> <form action="<%= Request.RawUrl %>" method="post"> <table border="0"> <tr><td>登录名:</td> <td><input type="text" name="loginName" style="width: 200px" value="Fish" /></td></tr> <tr><td>UserId:</td> <td><input type="text" name="UserId" style="width: 200px" value="78" /></td></tr> <tr><td>GroupId:</td> <td><input type="text" name="GroupId" style="width: 200px" /> 1表示管理员用户 </td></tr> <tr><td>用户全名:</td> <td><input type="text" name="UserName" style="width: 200px" value="Fish Li" /></td></tr> </table> <input type="submit" name="CustomizeLogin" value="登录" /> </form></fieldset>登录处理代码:
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<UserInfo>.SignIn(loginName, userinfo, 100); TryRedirect(); }显示用户信息的页面代码:
<fieldset><legend>用户状态</legend><form action="<%= Request.RawUrl %>" method="post"> <% if( Request.IsAuthenticated ) { %> 当前用户已登录,登录名:<%= Context.User.Identity.Name.HtmlEncode() %> <br /> <% var user = Context.User as MyFormsPrincipal<UserInfo>; %> <% if( user != null ) { %> <%= user.UserData.ToString().HtmlEncode() %> <% } %> <input type="submit" name="Logon" value="退出" /> <% } else { %> <b>当前用户还未登录。</b> <% } %> </form></fieldset>为了能让上面的页面代码发挥工作,必须在页面显示前重新设置HttpContext.User对象。
为此,我在Global.asax中添加了一个事件处理器:
在多台服务器之间使用Forms身份认证
默认情况下,ASP.NET 生成随机密钥并将其存储在本地安全机构 (LSA) 中,因此,当需要在多台机器之间使用Forms身份认证时,就不能再使用随机生成密钥的方式, 需要我们手工指定,保证每台机器的密钥是一致的。
用于Forms身份认证的密钥可以在web.config的machineKey配置节中指定,我们还可以指定加密解密算法:
<machineKey decryption="Auto" [Auto | DES | 3DES | AES] decryptionKey="AutoGenerate,IsolateApps" [String] />关于这二个属性,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("<span>Fish</span>") > 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("<span>Fish</span>") > 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身份认证有兴趣,那么请继续关注相关文章。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
ASP.NET提供了3种认证方式:windows身份验证、Forms验证和Passport验证。windows身份验证:IIS根据应用程序的设置执行身份验证。要
本文实例讲述了ASP.NET实现基于Forms认证的WebService应用方法。分享给大家供大家参考。具体实现方法如下:在安全性要求不是很高的ASP.Net程
在Asp.Net框架中提供了几种身份验证方式:Windows身份验证、Forms身份验证、passport身份验证(单点登录验证)。每种验证方式都有适合它的场景
大家在使用ASP.NET的时候一定都用过FormsAuthentication做登录用户的身份认证,FormsAuthentication的核心就是Cookie
ASP.NET技术开发中的安全问题,可通过识别用户身份、控制访问权限以及加密数据等方式提高网站开发安全的有效性。1、识别用户身份ASP.NET网站的识别方式有W