很多用户在开发 ASP.NET 应用程序时都有这样的需求:管理员角色的账户使用管理员的登录界面进行登录,普通用户角色的账户使用普通用户的登录界面进行登录。由于ASP.NET的 web.config里只能使用一个 authentication mode="Forms" 节点,所以,要实现不同用户采用不同的登录界面,一个办法就是创建一个管理员专用的虚拟目录,并设置为应用程序来实现。下面介绍另外一种采用重定向的办法 来解决这个问题。
本文介绍的方法原理是根据登录界面的返回地址进行判断,然后重定向到不同的页面。下面就是实现的详细过程。
1,创建一个网站,在网站里创建Admin文件夹和User文件夹,分别存放admin和普通用户所使用的文件。也可以只设置一个 Admin 文件夹。由于本方法采用的判断返回路径的方法,所以,要能从路径中区分出哪些是admin用户使用的文件夹。当然,采用其他的判断方法也是可以的。
2,在网站根目录下分别创建3个登录文件:Login.aspx、UserLogin.aspx和AdminLogin.aspx。其中 Login.aspx文件起地址转换的作用,
Login.aspx文件的主要内容:
- protected void Page_Load( object sender, EventArgs e)
- {
- String ReturnUrl = Request.QueryString["ReturnUrl" ];
- if (ReturnUrl == null || ReturnUrl.Equals(String.Empty))
- {
- //默认情况下,按普通用户进行登录
- Response.Redirect("~/UserLogin.aspx" );
- }
- else
- {
- if (ReturnUrl.ToLower().Contains( "/admin/" ))
- {
- Response.Redirect("~/AdminLogin.aspx?ReturnUrl=" + Server.UrlEncode(ReturnUrl));
- }
- else
- {
- Response.Redirect("~/UserLogin.aspx?ReturnUrl=" + Server.UrlEncode(ReturnUrl));
- }
- }
protected void Page_Load(object sender, EventArgs e) { String ReturnUrl = Request.QueryString["ReturnUrl"]; if (ReturnUrl == null || ReturnUrl.Equals(String.Empty)) { //默认情况下,按普通用户进行登录 Response.Redirect("~/UserLogin.aspx"); } else { if (ReturnUrl.ToLower().Contains("/admin/")) { Response.Redirect("~/AdminLogin.aspx?ReturnUrl=" + Server.UrlEncode(ReturnUrl)); } else { Response.Redirect("~/UserLogin.aspx?ReturnUrl=" + Server.UrlEncode(ReturnUrl)); } }
在这个文件的代码中,如果ReturnUrl中含有"/admin/",就重定向到AdminLogin.aspx登录界面;否则,就重定向到 UserLogin.aspx 登录界面。
UserLogin.aspx这个文件的内容如下:
- <%@ Page Language= "C#" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
- <mce:script runat="server" ><!--
- protected void Button1_Click( object sender, EventArgs e)
- {
- //密码验证过程在此省略,假如用户名是mxh,密码是mengxianhui
- String UserName = "mxh" ;
- FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, //票证的版本号
- UserName,//与身分验证票关联的用户名
- DateTime.Now, //票证发出时的本地 日期和时间
- DateTime.Now.AddHours(1),//票证过期的本地日期和时间
- true , // 如果票证存储在持久性cookie中(跨浏览器会话保存)则为 true 否则为false 如果票证储存在 URL中,将忽略此值
- "reader" , //储存在 票证中持定的用户信息,本页面供 reader 登录使用
- FormsAuthentication.FormsCookiePath //票证储存在cookie中的路径
- );
- //如果 forms 元素的 protection 属性设置为 All 或 Encryption,则窗体身份验证 使用 Encrypt 方法对窗体身份验证票进行加密和签名。
- string encTicket = FormsAuthentication.Encrypt(ticket);
- HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
- Response.Cookies.Add(cookie);
- Response.Redirect(FormsAuthentication.GetRedirectUrl(UserName, true ));
- }
- // --></mce:script>
- <html xmlns="http://www.w3.org/1999/xhtml" >
- <head runat="server" >
- <title>孟宪会之多用户登录测试页面</title>
- </head>
- <body>
- <form id="form1" runat= "server" >
- 普通用户登录界面省略<br />
- <asp:Button ID="Button1" runat= "server" OnClick= "Button1_Click" Text= "普通用户登录" />
- </form>
- </body>
- </html>
<%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <mce:script runat="server"><!-- protected void Button1_Click(object sender, EventArgs e) { //密码验证过程在此省略,假如用户名是mxh,密码是mengxianhui String UserName = "mxh"; FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2,//票证的版本号 UserName,//与身分验证票关联的用户名 DateTime.Now, //票证发出时的本地日期和时间 DateTime.Now.AddHours(1),//票证过期的本地日期和时间 true,// 如果票证存储在持久性cookie中(跨浏览器会话保存)则为 true 否则为false 如果票证储存在URL中,将忽略此值 "reader",//储存在票证中持定的用户信息,本页面供 reader 登录使用 FormsAuthentication.FormsCookiePath //票证储存在cookie中的路径 ); //如果 forms 元素的 protection 属性设置为 All 或 Encryption,则窗体身份验证使用 Encrypt 方法对窗体身份验证票进行加密和签名。 string encTicket = FormsAuthentication.Encrypt(ticket); HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); Response.Cookies.Add(cookie); Response.Redirect(FormsAuthentication.GetRedirectUrl(UserName, true)); } // --></mce:script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>孟宪会之多用户登录测试页面</title> </head> <body> <form id="form1" runat="server"> 普通用户登录界面省略<br /> <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="普通用户登录" /> </form> </body> </html>
这个文件将验证信息保存后,返回最初的请求页面。注意:这里连接数据库验证用户名和密码的过程省略过去了。
AdminLogin.aspx这个文件的全部内容如下:
- <%@ Page Language= "C#" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
- <mce:script runat="server" ><!--
- protected void Button1_Click( object sender, EventArgs e)
- {
- //密码验证过程在此省略,假如用户名是Admin,密码是mengxianhui
- FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, //票证的版本号
- "Admin" , //与身分验证票关联的用户名
- DateTime.Now, //票证发出时的本地日期和 时间
- DateTime.Now.AddHours(1),//票证过期的本地日期和时间
- true , // 如果票证存储在持久性cookie中(跨浏览器会话保存)则为 true 否则为false 如果票证储存在 URL中,将忽略此值
- "admin|manager|editor" , //储存在票证中持定的用户信息,本页面供 admin,manager,editor登录使用
- FormsAuthentication.FormsCookiePath //票证储存在cookie中的路径
- );
- //如果 forms 元素的 protection 属性设置为 All 或 Encryption,则窗体身份验证 使用 Encrypt 方法对窗体身份验证票进行加密和签名。
- string encTicket = FormsAuthentication.Encrypt(ticket);
- HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
- Response.Cookies.Add(cookie);
- Response.Redirect(FormsAuthentication.GetRedirectUrl("Admin" , true ));
- }
- // --></mce:script>
- <html xmlns="http://www.w3.org/1999/xhtml" >
- <head runat="server" >
- <title>孟宪会之多用户登录测试页面</title>
- </head>
- <body>
- <form id="form1" runat= "server" >
- 管理员登录界面,省略
- <asp:Button ID="Button1" runat= "server" Text= " 登 录 " OnClick= "Button1_Click" />
- </form>
- </body>
- </html>
<%@ Page Language="C#" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <mce:script runat="server"><!-- protected void Button1_Click(object sender, EventArgs e) { //密码验证过程在此省略,假如用户名是Admin,密码是mengxianhui FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2,//票证的版本号 "Admin",//与身分验证票关联的用户名 DateTime.Now, //票证发出时的本地日期和时间 DateTime.Now.AddHours(1),//票证过期的本地日期和时间 true,// 如果票证存储在持久性cookie中(跨浏览器会话保存)则为 true 否则为false 如果票证储存在URL中,将忽略此值 "admin|manager|editor",//储存在票证中持定的用户信息,本页面供 admin,manager,editor登录使用 FormsAuthentication.FormsCookiePath //票证储存在cookie中的路径 ); //如果 forms 元素的 protection 属性设置为 All 或 Encryption,则窗体身份验证使用 Encrypt 方法对窗体身份验证票进行加密和签名。 string encTicket = FormsAuthentication.Encrypt(ticket); HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); Response.Cookies.Add(cookie); Response.Redirect(FormsAuthentication.GetRedirectUrl("Admin", true)); } // --></mce:script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>孟宪会之多用户登录测试页面</title> </head> <body> <form id="form1" runat="server"> 管理员登录界面,省略 <asp:Button ID="Button1" runat="server" Text=" 登 录 " OnClick="Button1_Click" /> </form> </body> </html>
注意:这里连接数据库验证用户名和密码的过程省略过去了。
3,在Global的AuthenticateRequest 事件(一定要注意:不是 AuthorizeRequest 事件)里将角色信息附加到当前用户的上下文中。
- protected void Application_AuthenticateRequest( object sender, EventArgs e)
- {
- string cookieName = FormsAuthentication.FormsCookieName;
- HttpCookie authCookie = Context.Request.Cookies[cookieName];
- if ( null == authCookie)
- {
- return ;
- }
- FormsAuthenticationTicket authTicket = null ;
- try
- {
- authTicket = FormsAuthentication.Decrypt(authCookie.Value);
- }
- catch (Exception ex)
- {
- return ;
- }
- if ( null == authTicket)
- {
- return ;
- }
- FormsIdentity id = new FormsIdentity(authTicket);
- String[] roles = id.Ticket.UserData.Split('|' ); //读出在登录时设 置的角色列表。
- System.Security.Principal.GenericPrincipal principal = new System.Security.Principal.GenericPrincipal(id, roles);
- Context.User = principal;//将验证信息 附加到当前用户上下文。
- }
protected void Application_AuthenticateRequest(object sender, EventArgs e) { string cookieName = FormsAuthentication.FormsCookieName; HttpCookie authCookie = Context.Request.Cookies[cookieName]; if (null == authCookie) { return; } FormsAuthenticationTicket authTicket = null; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch (Exception ex) { return; } if (null == authTicket) { return; } FormsIdentity id = new FormsIdentity(authTicket); String[] roles = id.Ticket.UserData.Split('|'); //读出在登录时设置的角色列表。 System.Security.Principal.GenericPrincipal principal = new System.Security.Principal.GenericPrincipal(id, roles); Context.User = principal;//将验证信息附加到当前用户上下文。 }
4,在web.config文件中,允许登录文件的匿名访问,以便在未登录的情况下显示登录界面,注意:如果包含图片、css等文件,也需要设置这 些资源允许匿名访问。
- < configuration >
- < location path = "AdminLogin.aspx" >
- < system.web >
- < authorization >
- < allow users = "?" />
- </ authorization >
- </ system.web >
- </ location >
- < location path = "UserLogin.aspx" >
- < system.web >
- < authorization >
- < allow users = "?" />
- </ authorization >
- </ system.web >
- </ location >
- < system.web >
- < authentication mode = "Forms" >
- < forms loginUrl = "Login.aspx" path = "/" protection = "Encryption" > </ forms >
- </ authentication >
- < authorization >
- < deny users = "?" />
- < allow users = "*" />
- </ authorization >
- </ system.web >
- </ configuration >
<configuration> <location path="AdminLogin.aspx"> <system.web> <authorization> <allow users="?"/> </authorization> </system.web> </location> <location path="UserLogin.aspx"> <system.web> <authorization> <allow users="?"/> </authorization> </system.web> </location> <system.web> <authentication mode="Forms"> <forms loginUrl="Login.aspx" path="/" protection="Encryption"></forms> </authentication> <authorization> <deny users="?"/> <allow users="*"/> </authorization> </system.web> </configuration>
5,这样,当访问admin文件夹下的内容时,会直接转到AdminLogin.aspx界面。在登录之后,就可以在/Admin/文件夹下的页面 中使用下面的方法得到当前登录的用户名和所具有的角色,根据角色来判断当前用户是否有权操作:
- Response.Write( "<li>当前登录用户 = " + Page.User.Identity.Name);
- Response.Write("<li>admin = " + Page.User.IsInRole( "admin" ));
- Response.Write("<li>reader = " + Page.User.IsInRole( "reader" ));
Response.Write("<li> 当前登录用户 = " + Page.User.Identity.Name); Response.Write("<li>admin = " + Page.User.IsInRole("admin")); Response.Write("<li>reader = " + Page.User.IsInRole("reader"));
为了简单起见,可以写一个Admin使用的基类页面,统一在基类页面中进行权限的处理。