ASP.NET安全認(rèn)證機制基于角色窗體的實現(xiàn)
簡介:
ASP.NET 中窗體認(rèn)證是一個功能非常強大的特性,只需要很少的代碼就可以實現(xiàn)一個簡單的平臺無關(guān)的ASP.NET安全認(rèn)證機制。 但是,如果你需要一個更復(fù)雜更有效的認(rèn)證機制,那么你就要把眾多用戶分成用戶群組,以利用它的靈活性。
Windows 集成認(rèn)證提供了這種認(rèn)證機制,但它使用的是 NTLM,即Windows NT LAN Manager,因而它不是跨平臺的。現(xiàn)在越來越多的人使用 Linux 系統(tǒng),而 Mozilla Forefox 瀏覽器用戶也越來越多,我們肯定不能把這些人拒之門外,因此我們尋求另外的ASP.NET安全認(rèn)證機制。有兩個選擇:
一是為網(wǎng)站劃分多個區(qū)域,提供多個登錄頁面,強迫用戶一個一個的去注冊和登錄;
二是把用戶分組,并且限制特定用戶組對某頁面或者某區(qū)域訪問的權(quán)限。
后者當(dāng)然是更好的選擇。通過分配角色給各個用戶,我們能夠?qū)崿F(xiàn)這種功能。
微軟為.NET平臺留下了窗體認(rèn)證中基于角色的認(rèn)證機制,但是我們必須自己去實現(xiàn)它。本文力求覆蓋窗體認(rèn)證中基于角色的認(rèn)證機制的一些基本的東西,比如它的概念,它的實現(xiàn),如何在Web應(yīng)用程序中應(yīng)用等。
必要準(zhǔn)備:
我們首先要建立一個數(shù)據(jù)庫,一個Web應(yīng)用項目,幾個不同安全級別的機密目錄,以及幾個ASP.NET頁面。當(dāng)然你也可以在你現(xiàn)有的Web應(yīng)用項目中添加這些。
1、創(chuàng)建數(shù)據(jù)庫
首先要選擇你需要使用的數(shù)據(jù)庫管理系統(tǒng) DBMS。本文使用 SQL Server 2000。
在實際應(yīng)用項目的數(shù)據(jù)庫中,一般都會有用戶數(shù)據(jù)表 Users,它可能包括用戶唯一標(biāo)記:UserID,用戶名:UserName,密碼:Password,用戶的郵件地址:Email,用戶所在城市:City,用戶登錄次數(shù) LoginCount 等??梢酝ㄟ^創(chuàng)建一個 UserInRoles 數(shù)據(jù)表(一般可以包括兩個字段,用戶名:UserName,用戶角色:UserRoles)來實現(xiàn)為用戶分配角色。
為了簡單,我只創(chuàng)建一個 Users 數(shù)據(jù)表,它有3個字段,用戶名 UserName,密碼 Password,用戶角色 UserRoles。創(chuàng)建表之前,你要選擇數(shù)據(jù)庫,或者創(chuàng)建一個新的數(shù)據(jù)庫。要創(chuàng)建一個新的命名為WebSolution的數(shù)據(jù)庫 ,只需要簡單的SQL語句:
- Create DATABASE WebSolution
- GO
- 要選擇一個叫msdb的數(shù)據(jù)庫,可以使用SQL語句:
- USE msdb
- GO
- 接下來,我們創(chuàng)建剛才提到的 Users 數(shù)據(jù)表,
- SQL 腳本如下:
- Create TABLE Users
- (
- UserName nvarchar(100) CONSTRAINT
- PK_UserName PRIMARY KEY,
- Password nvarchar(150),
- UserRoles nvarchar(100)
- )
- 可以為這個表創(chuàng)建索引 Credentials,SQL語句如下:
- Create INDEX Credentials ON Users
- (
- UserName,
- Password
- )
是否創(chuàng)建索引是可選的,由你自己決定。索引的好處和壞處請參考相關(guān)資料。
然后我們?yōu)檫@個Users數(shù)據(jù)庫添加數(shù)據(jù)。角色名稱由你自己自由選擇,但是最好用有意義的名稱,
比如"Administrator"(頂級管理員),"Manager"(管理員),"Member"(加盟成員),"User"(普通用戶)等。
例如:
- UserName|Password|Roles
- "willmove"|"pwd123"|"Administrator,User"
- "amuhouse"|"pwd123"|"User"
- 其SQL語句是:
- --注意 '45CB41B32DCFB917CCD8614F1536D6DA'
- 是 'pwd123' 使用 md5 加密后的字符串
- Insert INTO Users(UserName,Password,UserRoles)
- VALUES ('willmove','45CB41B32DCFB917CCD8614F1536D6DA'
- ,'Administrator,User')
- GO
- Insert INTO Users(UserName,Password,UserRoles)
- VALUES ('amuhouse','45CB41B32DCFB917CCD8614F1536D6DA'
- ,'User')
- GO
要注意的是角色 Roles 是大小寫敏感的,這是因為在 Web.config 文件中是大小寫敏感的?,F(xiàn)在我們?yōu)閷崿F(xiàn)這個安全認(rèn)證機制創(chuàng)建幾個必要的頁面。
首先是用戶登錄頁面 Login.aspx
如果還沒有創(chuàng)建Web應(yīng)用程序,那就現(xiàn)在創(chuàng)建一個。當(dāng)然你也可以在一個已有的Web應(yīng)用程序中創(chuàng)建這個頁面。這里我假設(shè)已經(jīng)創(chuàng)建了一個名稱為 RolebasedAuth的Web應(yīng)用程序(即 Visual Studio .Net 中的Project)。我把這個Login.aspx放在它的根目錄下,也就是通過 http://localhost/RolebasedAuth/Login.aspx 可以訪問。這個Login.aspx放在哪里是無所謂的,但是在ASP.NET安全認(rèn)證機制中它必須是公眾有權(quán)限訪問的。
#p#
在應(yīng)用程序根路徑下,我們創(chuàng)建兩個機密的子目錄,分別是 Admin 和 User。
接下來,我們創(chuàng)建一個支持角色認(rèn)證的窗體的基于ASP.NET安全認(rèn)證機制的登錄系統(tǒng)。因為微軟沒有提供簡單的實現(xiàn)機制,我們要自己花些時間去創(chuàng)建認(rèn)證票據(jù)。它需要存貯少量信息,當(dāng)然,有些名稱必須和 Web.config 中配置的一樣,要不ASP.NET 就會認(rèn)為你的認(rèn)證票據(jù)是無效的,從而強制轉(zhuǎn)向到登錄頁面。我們在 VS.NET 中為 Login.aspx 添加兩個TextBox控件,取名 UserNameTextBox, PasswordTextBox,再添加一個Button,取名 LoginButton,點擊它進(jìn)入后臺代碼。
在 LoginButton_Click 方法中添加需要的代碼。如下:
- private void LoginButton_Click
- (object sender, System.EventArgs e)
- {
- // 初始化
- FormsAuthentication
- // 注意它是在
- System.Web.Security 命名空間
- // 因此要在代碼開始添加
- using System.Web.Security;
- FormsAuthentication.Initialize ();
- // 創(chuàng)建數(shù)據(jù)庫連接和數(shù)據(jù)庫操作命令對象
- // 注意它是在
- System.Data.SqlClient 命名空間
- // 因此要在代碼開始處添加
- using System.Data.SqlClient;
- SqlConnection conn =
- new SqlConnection("Data
- Source=sun-willmove;integrated
- security=SSPI;
- Initial Catalog=WebSolution;");
- SqlCommand cmd = conn.CreateCommand();
- cmd.CommandText = "Select UserRoles
- FROM Users Where UserName=@username " +
- "AND Password=@password";
- // 填充各個參數(shù)
- cmd.Parameters.Add("@username",
- SqlDbType.NVarChar, 100).Value =
- UserNameTextBox.Text;
- cmd.Parameters.Add("@password",
- SqlDbType.NVarChar, 150).Value =
- FormsAuthentication.
- HashPasswordForStoringInConfigFile(
- PasswordTextBox.Text, "md5");
- // 或者 "sha1"
- // 執(zhí)行數(shù)據(jù)庫操作命令
- conn.Open();
- SqlDataReader reader = cmd.ExecuteReader();
- if (reader.Read())
- {
- // 為了實現(xiàn)認(rèn)證,創(chuàng)建一個新的票據(jù)
- FormsAuthenticationTicket ticket = new
- FormsAuthenticationTicket(
- 1, // 票據(jù)版本號
- UserNameTextBox.Text, // 票據(jù)持有者
- DateTime.Now, //分配票據(jù)的時間
- DateTime.Now.AddMinutes(30), // 失效時間
- true, // 需要用戶的 cookie
- reader.GetString(0), // 用戶數(shù)據(jù),
- 這里其實就是用戶的角色
- FormsAuthentication.FormsCookiePath);
- //cookie有效路徑
- //使用機器碼machine key加密cookie,
- 為了安全傳送
- string hash = FormsAuthentication.
- Encrypt(ticket);
- HttpCookie cookie = new HttpCookie(
- FormsAuthentication.FormsCookieName,
- // 認(rèn)證cookie的名稱
- hash); //加密之后的cookie
- //將cookie的失效時間設(shè)置為和
- 票據(jù)tikets的失效時間一致
- if (ticket.IsPersistent) cookie.
- Expires = ticket.Expiration;
- //添加cookie到頁面請求響應(yīng)中
- Response.Cookies.Add(cookie);
- // 將用戶轉(zhuǎn)向到之前請求的頁面,
- // 如果之前沒有請求任何頁面,就轉(zhuǎn)向到首頁
- string returnUrl = Request.QueryString
- ["ReturnUrl"];
- if (returnUrl == null) returnUrl = "./";
- // 不要調(diào)用 FormsAuthentication.
- RedirectFromLoginPage 方法,
- // 因為它會把剛才添加的票據(jù)(cookie)替換掉
- Response.Redirect(returnUrl);
- }
- else
- {
- // 不要告訴用戶"密碼錯誤",
- 這樣等于給了入侵者一個機會,
- // 因為他們知道了他們輸入的用戶名是存在的
- //
- ErrorLabel.Text = "用戶名或者密碼錯誤,請重試!";
- ErrorLabel.Visible = true;
- }
- reader.Close();
- conn.Close();
- }
- 前臺 aspx 頁面代碼如下:
- 〈 %@ Page language="c#" Codebehind="Login.aspx.cs"
- AutoEventWireup="false"
- Inherits="RolebasedAuth.Login" %〉
- 〈 !DOCTYPE HTML PUBLIC "-//W3C
- //DTD HTML 4.0 Transitional//EN" 〉
- 〈 HTML〉
- 〈 HEAD〉
- 〈 title〉Login〈 /title〉
- 〈 meta name="GENERATOR" Content="Microsoft
- Visual Studio .NET 7.1"〉
- 〈 meta name="CODE_LANGUAGE" Content="C#"〉
- 〈 meta name="vs_defaultClientScript"
- content="JavaScript"〉
- 〈 meta name="vs_targetSchema"
- content="http://schemas.microsoft.com/
- intellisense/ie5 "〉
- 〈 /HEAD〉
- 〈 body〉
- 〈 form id="Form1" method="post" runat="server"〉
- 〈 P〉
- 〈 asp:Label id="Label1" runat="server"〉
- 用戶名:〈 /asp:Label〉
- 〈 asp:TextBox id="UserNameTextBox" runat="server"〉
- 〈 /asp:TextBox〉〈 /P〉
- 〈 P〉〈 FONT face="宋體"〉 〈 /FONT〉
- 〈 asp:Label id="Label2" runat="server"〉
- 密碼:〈 /asp:Label〉
- 〈 asp:TextBox id="PasswordTextBox" runat="server"
- TextMode="Password"〉〈 /asp:TextBox〉〈 /P〉
- 〈 P〉
- 〈 asp:Label id="ErrorLabel" runat="server"
- Visible="False"〉
- 〈 /asp:Label〉〈 /P〉
- 〈 P〉
- 〈 asp:Button id="LoginButton" runat="server"
- Text="登錄"〉
- 〈 /asp:Button〉〈 /P〉
- 〈 /form〉
- 〈 /body〉
- 〈 /HTML〉
#p#
你會注意到上面我們對密碼的處理:將它哈希加密。哈希加密是一種單向算法(不可逆算法),生成唯一的字符數(shù)組。因此即使是改變密碼中一個字母的大小寫,都會生成完全不同的哈希列。我們把這些加密的密碼存儲在數(shù)據(jù)庫中,這樣更安全。在實際應(yīng)用中,你可能想為用戶找回忘記的密碼。但是哈希散列是不可逆的,所以你就不可能恢復(fù)原來的密碼。但是你可以更改用戶的密碼,并且把這個更改后的密碼告訴他。如果一個網(wǎng)站能夠給你舊密碼,那么你要考慮清楚了,你的用戶數(shù)據(jù)是不安全的!事實上,國內(nèi)大部分網(wǎng)站都是沒有經(jīng)過加密直接把用戶的密碼存儲到數(shù)據(jù)庫中的。如何一個黑客入侵成功,那么這些用戶帳戶就很危險了!
如果沒有使用SSL,你的密碼在網(wǎng)絡(luò)中也是以明文傳輸?shù)?。傳輸過程中可能會被竊取。在服務(wù)器端加密密碼只能保證密碼存儲的安全。SSL相關(guān)的資料可以在 http://www.versign.com或 http://www.thewte.com 中找到。
如果你不想以加密方式在數(shù)據(jù)庫中存儲密碼,你可以更改上面的代碼,把
- FormsAuthentication.HashPasswordForStoringInConfigFile
- (PasswordTextBox.Text, "md5") 改成
- PasswordTextBox.Text 即可。
下一步,我們需要修改 Global.asax 文件。如果你的Web應(yīng)用程序沒有這個文件,請右鍵單擊Web應(yīng)用項目,選擇 "添加->添加新項...->Global Application Class"。在 Global.asax 或者 Global.asax.cs 中,找到叫做 Application_AuthenticationRequest 的方法(函數(shù))。先要確認(rèn)已經(jīng)包含或者使用了 System.Security.Principal 以及 System.Web.Security 命名空間,然后修改它,修改后的代碼:
- protected void Application_AuthenticateRequest
- (Object sender, EventArgs e)
- {
- if (HttpContext.Current.User != 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;
- // 取存儲在票據(jù)中的用戶數(shù)據(jù),
- 在這里其實就是用戶的角色
- string userData = ticket.UserData;
- string[] roles = userData.Split(',');
- HttpContext.Current.User = new
- GenericPrincipal(id, roles);
- }
- }
- }
- }
認(rèn)證票據(jù)(用戶名和密碼)是沒有作為cookie的一部分來存儲的,而且也不可以,因為用戶可以修改他們的cookie。
事實上,F(xiàn)ormsAuthentication是用你的機器碼 (machine key,通常在 machine.config 中)來加密票據(jù)(FormsAuthenticationTicket)的。我們使用 UserData 存儲用戶角色,并且生成一個新的憑證。一旦憑證已經(jīng)創(chuàng)建,它會被添加到當(dāng)前上下文中(即 HttpContext),這樣就可以用它來取回用戶角色了。
接下來,我們設(shè)置機密目錄(也就是"安全目錄",特定的使用者如管理員才有權(quán)限訪問的目錄)。首先看看你的Web應(yīng)用程序根目錄下是否有 Web.config 這個文件,如果沒有就創(chuàng)建一個。你也可以在你的子目錄中創(chuàng)建 Web.config 文件,當(dāng)然,這個 Web.config 文件是有限制的(一些參數(shù)它不可以設(shè)置)。要實現(xiàn)安全認(rèn)證,在 Web應(yīng)用程序根目錄下的 Web.config 文件中找到 〈system.web〉 節(jié)點下的
- 〈authentication mode="Windows" /〉,
- 把它修改為
- 〈authentication mode="Forms" 〉
- 〈forms name="AMUHOUSE.ASPXAUTH"
- loginUrl="Login.aspx"
- protection="All"
- path="./" /〉
- 〈/authentication〉
- 〈authorization〉
- 〈allow users="*"/〉
- 〈/authorization 〉
上面的 name="AMUHOUSE.ASPXAUTH" 中,AMUHOUSE.ASPXAUTH 這個名稱是任意的。要控制用戶或者用戶組的權(quán)限,我們可以有兩種方法,一是配置在應(yīng)用程序根目錄下的 Web.config 文件,二是在機密目錄下創(chuàng)建一個獨立的 Web.config 文件。(后者也許會比較好。)如果是前者,這個Web.config 就應(yīng)該包含有下面的內(nèi)容(或者類似的內(nèi)容):
- 〈configuration 〉
- 〈system.web 〉
- 〈authentication mode="Forms" 〉
- 〈forms name=" AMUHOUSE.ASPXAUTH"
- loginUrl="login.aspx"
- protection="All"
- path="/"/ 〉
- 〈/authentication 〉
- 〈authorization 〉
- 〈allow users="*"/ 〉
- 〈/authorization 〉
- 〈/system.web 〉
- 〈location path="./Admin" 〉
- 〈system.web 〉
- 〈authorization 〉
- 〈!-- 注意!下面幾行的順序和大小寫是非常重要的! -- 〉
- 〈allow roles="Administrator"/ 〉
- 〈deny users="*"/ 〉
- 〈/authorization 〉
- 〈/system.web 〉
- 〈/location 〉
- 〈location path="./User" 〉
- 〈system.web 〉
- 〈authorization 〉
- 〈!-- 注意!下面幾行的順序和大小寫是非常重要的! -- 〉
- 〈allow roles="User"/ 〉
- 〈deny users="*"/ 〉
- 〈/authorization 〉
- 〈/system.web 〉
- 〈/location 〉
- 〈/configuration 〉
為了使Web應(yīng)用程序的目錄之前不互相依賴,可以比較方便的改名或者移動,可以選擇在每一個安全子目錄下配置單獨的 Web.config 文件。它只需要配置 〈authorization/〉節(jié)點,如下:
- 〈 configuration 〉
- 〈 system.web 〉
- 〈 authorization 〉
- 〈 !-- 注意!下面幾行的順序和大小寫是非常重要的! -- 〉
- 〈 allow roles="Administrator"/ 〉
- 〈 deny users="*"/ 〉
- 〈 /authorization 〉
- 〈 /system.web 〉
- 〈 /configuration 〉
需要再次提醒的是,上面的角色 roles 是大小寫敏感的,為了方便,你也可以把上面修改為:
- 〈 allow roles="Administrator,administrator" /〉
如果你想允許或者禁止多個角色對這個目錄的訪問,可以用逗號隔開,如:
- 〈 allow roles="Administrator,Member,User" /〉
- 〈 deny users="*" /〉
至此,我們已經(jīng)為網(wǎng)站配置了基于角色的ASP.NET安全認(rèn)證機制了。
你可以先編譯你的程序,然后嘗試訪問一個機密目錄,例如 http://localhost/RolebasedAuth/Admin,這時候你就會被轉(zhuǎn)向到用戶登錄頁面。如果你登錄成功,并且你的角色對這個目錄有訪問權(quán)限,你就重新回到這個目錄下??赡軙杏脩簦ɑ蛉肭终撸┢髨D進(jìn)入機密目錄,我們可以使用一個 Session 來存儲用戶登錄的次數(shù),超過一定次數(shù)就不讓用戶登錄,并且顯示"系統(tǒng)拒絕了你的登錄請求!"。
下面,我們討論如何根據(jù)用戶角色讓W(xué)eb控件顯示不同內(nèi)容。
有時候根據(jù)用戶的角色來顯示內(nèi)容比較好,因為你可能不想為那么多不同的角色(用戶群組)制作一大堆有許多重復(fù)內(nèi)容的頁面。這樣的網(wǎng)站,各種用戶帳戶可以并存,付費的用戶帳戶能夠訪問附加的付費內(nèi)容。另一個例子是一個頁面將顯示一個 "進(jìn)入后臺管理" 按鈕鏈接到后臺管理頁面如果當(dāng)前用戶是 "Administrator"(高級管理員)角色。我們現(xiàn)在就實現(xiàn)這個頁面。
#p#
我們上面用到的 GenericPrincipal 類實現(xiàn)了 IPincipal 接口,這個接口有一個方法名叫做 IsInRole(),它的參數(shù)是一個字符串,這個字符串就是要驗證的用戶角色。如果我們要顯示內(nèi)容給角色是 "Administrator"的已登錄用戶,我們可以在 Page_Load 中添加下面代碼: 程序代碼
- if (User.IsInRole("Administrator"))
- AdminLink.Visible = true;
- 整個的頁面代碼如下
- (為了簡便,把后臺代碼也寫在aspx頁面): 程序代碼
- 〈html 〉
- 〈head 〉
- 〈title 〉歡迎您!〈/title 〉
- 〈script runat="server" 〉
- protected void Page_Load(Object sender,
- EventArgs e)
- {
- if (User.IsInRole("Administrator"))
- AdminLink.Visible = true;
- else
- AdminLink.Visible = false;
- }
- 〈/script 〉
- 〈/head 〉
- 〈body 〉
- 〈h2 〉歡迎!〈/h2 〉
- 〈p 〉歡迎來到阿木小屋
- http://amuhouse.com/ ^_^〈/p 〉
- 〈asp:HyperLink id="AdminLink" runat="server"
- Text="管理首頁" NavigateUrl="./Admin"/ 〉
- 〈/body 〉
- 〈/html 〉
樣,鏈接到 Admin 目錄的HyperLink 控件只會顯示給角色是 Administrator 的用戶。你也可以根據(jù)為未登錄用戶提供一個鏈接到登錄頁面,如:程序代碼
- protected void Page_Load
- (object sender, System.EventArgs e)
- {
- if (User.IsInRole("Administrator"))
- {
- AdminLink.Text = "管理員請進(jìn)";
- AdminLink.NavigateUrl="./Admin";
- }
- else if(User.IsInRole("User"))
- {
- AdminLink.Text = "注冊用戶請進(jìn)";
- AdminLink.NavigateUrl="./User";
- }
- else
- {
- AdminLink.Text = "請登錄";
- AdminLink.NavigateUrl="Login.aspx?
- ReturnUrl=" + Request.Path;
- }
- }
這里,我們通過設(shè)置叫做ReturnUrl的 QueryString 變量,可以使用戶登錄成功后返回到當(dāng)前的這個頁面.
小結(jié):
本文用于幫助你理解基于角色安全機制的重要性、實用性,并且也通過ASP.NET安全認(rèn)證機制實現(xiàn)了基于角色的安全控制。它并不是一個很難實現(xiàn)的機制,不過它可能需要一些相關(guān)知識如 什么是用戶憑證,如何認(rèn)證用戶身份,以及如何審定授權(quán)用戶。如果你覺得它很有幫助,我會非常高興。我希望它可以引導(dǎo)你在你的網(wǎng)站中去實現(xiàn)基于角色的窗體安全認(rèn)證機制。
【編輯推薦】