作者:Keith Brown
相關技術:安全、ASP.NET 2.0
難度:★★☆☆☆
[導讀]在安全已經成為首要問題的今天,ASP.NET 2.0給了開發人員極大的信心,從新引入的伺服器段安全控制項、角色定義、密碼恢複及其基於成員和角色的編程,ASP.NET 2.0讓您的系統更加安全可靠。
新的安全性功能是ASP.NET 2.0中的一項重大改進。這些功能包括系統管理使用者帳戶資料庫的成員資格服務、雜湊密碼、系統管理使用者角色成員資格的角色管理器,以及可以更容易實現表單身分識別驗證的五個新的伺服器端控制項。ASP.NET 2.0還提供了提供者模型(Provider Model),使您能夠完全控制Membership和Role服務以及無cookie表單身分識別驗證的實現。您還可以非常輕鬆地對使用者帳戶和角色進行簡單的、基於Web的本地和遠端管理,並且可以獲得對其他非安全性相關設定的增強控制。
重推表單身分識別驗證
表單身分識別驗證是ASP.NET 1.0中使用最廣泛的功能之一,因為它封裝了許多特定實現所缺少的最佳實務。例如,您知道有多少表單身分識別驗證實現可以保護用來存放用戶端憑證的cookie的完整性嗎?表單身分識別驗證不僅將使用者名稱寫入該cookie中,而且還添加了一個訊息身分識別驗證代碼(一個根據該cookie形成的雜湊值和只有Web伺服器知道的秘密值)。這可以防止惡意的用戶端通過提高許可權或通過修改其cookie中的名稱來查看其他使用者的資料。
如果留意.NET Web開發人員推出的各種新聞群組和列表格服務器,您將瞭解到人們正在一遍又一遍地實現相同的東西:使用者資料庫、cookie中緩衝的角色、捕獲使用者名稱和密碼的控制項、系統管理使用者和角色的工具。ASP.NET小組已經提供了針對幾乎所有這些問題的內建解決方案。在研究ASP.NET 2.0的alpha版本時,我發現在以可管理的方式構建使用表單身分識別驗證的Web網站時,所使用的代碼量急劇減少。
入門
如果您有ASP.NET 2.0(可用於MSDN),那麼在演練某些可以執行的實驗時,您就會發現開始使用這些新功能是多麼地簡單!普通訂閱者可以下載。
要開始下載,您需要一個指向空目錄的虛擬目錄。您必須確保ASP.NET輔助進程具有讀取、執行和寫入該目錄的許可權。如果您啟動並執行是Windows 2000或Windows XP,則需要為ASP.NET本地帳戶賦予這些許可權;而對於Windows Server 2003,您需要為網路服務帳戶賦予這些許可權。
我將使用表單身分識別驗證,所以我需要通過web.config檔案來啟用它。如果我現在向您展示如何使用ASP.NET 1.1,那麼您需要開啟一個文字編輯器並開始手動鍵入XML。但是在ASP.NET 2.0中,我最喜歡的功能之一是互動式設定檔編輯器,它直接構建於IIS管理主控台,您可以在虛擬目錄的屬性工作表的“ASP.NET”選項卡中找到它。單擊“Edit configuration”按鈕以開啟編輯器。
圖1 配置編輯器
圖1顯示了這個新編輯器。您會看到我選擇了表單身分識別驗證而不是預設選項:Windows身分識別驗證。在您自己的虛擬目錄中進行同樣的操作。當使用組態工具時,將Web應用程式的預設語言設定為C#,因為它將替您省去一些後面需要進行的輸入。“Page Language Default”設定是Application選項卡上的第一個下拉選項。在應用這些更改之後,您將在目錄中找到web.config檔案並帶有所有設定。
您需要向Membership服務註冊一些使用者以便開始,因此編寫的第一頁是允許添加使用者的頁。在該測試版中提供了一個伺服器控制項,通過該控制項,您可以使用下面三行代碼來實現該頁:
<form runat='server'>
<asp:createuser runat='server'/>
</form>
<form runat='server'>
<table>
<tr><td>Name:</td><td><asp:textbox id='name' runat='server'/></td></tr>
<tr><td>Email address:</td><td><asp:textbox
id='email' runat='server'/>
</td></tr>
<tr><td>Password:</td><td><asp:textbox textmode='Password'
id='pwd1' runat='server'/>
</td></tr>
<tr><td>Retype password:</td><td><asp:textbox textmode='Password'
id='pwd2' runat='server'/>
</td></tr>
</table>
<asp:button text='Submit' onclick='onSubmit' runat='server'/>
<p><asp:label id='msg' runat='server'/></p>
</form>
<script runat='server'>
void onSubmit(object sender, EventArgs args)
{
if (pwd1.Text.Equals(pwd2.Text))
{
MembershipCreateStatus status;
MembershipUser newUser = Membership.CreateUser(name.Text,
pwd1.Text, email.Text, out status);
msg.Text = status.ToString();
}
else
msg.Text = "Passwords don't match, try again.";
}
</script>
圖2 adduser.aspx
不過,由於我使用的版本是alpha,所以我必須直接使用Membership類來手工為這個特殊的表單編寫代碼。而現在,只需使用圖2 中所示的ASPX頁就可以了,我將在本文稍後討論Membership類。圖3顯示當您將瀏覽器指向該頁時所看到的內容。繼續進行實驗,現在添加一些使用者和密碼。成功後,您的工作應該更加輕鬆!
圖3 成員頁
完成使用者添加之後,請仔細查看虛擬目錄。您應該看到一個名為“DATA”新的子目錄,其中包含了一個Microsoft Access資料庫。這是Membership和Role服務預設儲存其資料的地方,但是稍後我會向您展示如何覆蓋預設儲存機制以使用SQL Server,或您自己的自訂資料儲備庫。現在,您可以使用ASP.NET 2.0中的安全控制項了。
伺服器端安全控制項
圖4 列出了ASP.NET 2.0中的五個新安全控制項。從LoginStatus控制項開始探索是個好主意。首先建立一個包含該控制項的新ASPX頁。為了簡單起見,調用新頁面default.aspx:
<form runat='server'>
<asp:loginstatus runat='server'/>
</form>
| 控制項 |
描述 |
| LoginStatus |
提供一個登入或者登出的按鈕,主要依賴於使用者的目前狀態 |
| Login |
提供一個表單用於收集和校正依賴於使用者資料庫的登入憑證 |
| LoginName |
顯示一個已經登入的使用者名稱 |
| LoginView |
顯示特定的內容,依賴於使用者是否登入,而使用者是否是一個角色的成員是可選的 |
| PasswordRecovery |
提供一個找回忘記的密碼需要的清單 |
圖4 ASP.NET 2.0新的安全控制項
將瀏覽器指向該頁面,您應該看到一個Login連結。如果您在瀏覽器中查看結果頁面的原始碼,您將看到這個超級連結指向一個名為login.aspx的頁面,但您還沒有編寫它。這又是一個用三行代碼實現的Web頁,因此我們繼續進行實驗,現在就建立它:
<form runat='server'>
<asp:login runat='server'/>
</form>
如果您曾經手工實現過表單身分識別驗證,您就會讚賞這三行代碼。過去,執行資料庫尋找的等同實現需要兩倍數量的代碼。
現在回到您的瀏覽器,並單擊Login連結,它將您帶到5所示的登入頁。嘗試用一個無效使用者名稱或密碼登入,可以發現,系統會彈出一條適當的預設錯誤訊息。這條訊息不會給攻擊者太多的資訊。而一個沒有經驗的開發人員也決不會無意中發送回一條訊息給該使用者,告訴他獲得了正確的使用者名稱,請嘗試猜測另一個密碼!
圖5 登入頁
繼續進行並鍵入一個有效使用者名稱和密碼,這是您先前通過adduser.aspx頁輸入的使用者名稱和密碼,然後您應該被重新導向回default.aspx頁。由於您沒有為登入控制項提供任何自訂動作,所以預設情況下它只通過表單身分識別驗證來讓您登入,這意味著您的瀏覽器現在有了一個存放使用者名稱的加密cookie。
既然您已經重新導向回default.aspx頁面,您看到有什麼不同嗎?登入狀態控制項現在應該顯示Logout而不是Login。因為表單身分識別驗證cookie是與請求一起發送的,所以FormsAuthenticationModule建立了一個經過身分識別驗證的使用者主體,並且將其與該請求的上下文相關聯。登入狀態控制項會注意到這種情況,並且改變成允許您登出。嘗試登出並重新登入來查看這項工作。
現在,讓我們再添加一些代碼到default.aspx頁面:
<h3>User Name: <%= User.Identity.Name %></h3>
<h3>User Type: <%= User.GetType() %></h3>
重新整理這個頁面,會看到您用來登入的使用者名稱。請注意,表示使用者的基本對象是GenericPrincipal類型,這是FormsAuthenticationModule表示使用者的方式。一旦您啟動Role Manager,就會注意到這種類型變化,因為當啟用時,新的RoleManagerModule就取代了由FormsAuthentication使用它自己的類型產生的使用者。
現在,讓我們添加一個LoginView控制項到default.aspx頁面,以顯示可以根據使用者的登入而改變的內容。使用這個控制項最簡單的方法是提供兩個內容塊:一個用於匿名請求(在使用者登入之前),另一個用於身分識別驗證請求(在使用者登入之後):
<asp:loginview runat='server'>
<anonymoustemplate>
<h4>If you see this, you've not yet logged in!</h4>
</anonymoustemplate>
<loggedintemplate>
<h4>Welcome to my website, <asp:loginname runat='server'/>!</h4>
</loggedintemplate>
</asp:loginview>
當您登入或登出時,您應該看到LoginView控制項中的文本發生了改變,正如我們所預料的一樣。這是一個非常簡單的想法,但是它確實讓您的代碼變得更清晰。
定義角色
我已經製作了一個簡單的頁面,它允許您使用Role Manager將使用者添加到角色,但是在您能使用它之前,還需要為應用程式啟用Role Manager。回到組態工具,並找到“Authentication”選項卡。選中標有“Role management enabled”的複選框,然後應用這個改變。
<form runat='server'>
<table>
<tr><td>Role:</td><td><asp:textbox id='role' runat='server'/></td></tr>
<tr><td>User:</td><td><asp:textbox id='user' runat='server'/></td></tr>
</table>
<asp:button text='Add user to role!' onclick='onSubmit' runat='server'/>
<p><asp:label id='msg' runat='server' viewstateenabled='false'/></p>
</form>
<script runat='server'>
void onSubmit(object sender, EventArgs args)
{
if (!Roles.RoleExists(role.Text))
{
Roles.CreateRole(role.Text);
msg.Text = "Created a new role.";
}
Roles.AddUserToRole(user.Text, role.Text);
}
</script>
圖6 addrole.aspx
在圖6 中顯示了addrole.aspx頁面的代碼,而圖7顯示了表單的外觀。將這個頁面放到虛擬目錄中並且將瀏覽器指向它,這樣您就可以添加了一些角色。指定一個使用者名稱(您前面通過adduser.aspx表單添加的使用者名稱)和一個角色名稱,然後按下按紐將使用者添加到角色中。代碼將首先添加角色(如果它不存在的話),然後將使用者添加到角色。在後台,Role Manager會在Membership服務使用的同一Microsoft Access資料庫中跟蹤這些角色映射,但是這實際上只是巧合。Role Manager可以將其資料存放區在SQL Server或任何其他的儲存中,並且不必使用與Membership服務相同的機制。為此,Membership和Role Manager的提供者模型完全不同。
圖7 添加角色
如果您曾經在ASP.NET中實現了自訂角色,則一定會欣賞內建的Role Manager,因為您不再需要成為ASP.NET HTTP管道的主管,就能實現角色型安全性。一旦您添加了一些角色,您就可以回到default.aspx,並且可以使用LoginView控制項來做一些有趣的事情。在<loggedintemplate/>元素之後添加另一個部分:
<rolegroups>
<asp:rolegroup roles='ForumModerators'>
<contenttemplate>
<h4>Controls for forum moderators go here.</h4>
</contenttemplate>
</asp:rolegroup>
<asp:rolegroup roles='Friends'>
<contenttemplate>
<h4>Welcome, friend!</h4>
</contenttemplate>
</asp:rolegroup>
</rolegroups>
您可能沒有選擇與我相同的角色,因此,需要用您自己的角色名稱來代替我的角色名稱,並且調整內容使之適合角色。完成後,就可以通過使用不同角色中的不同使用者帳戶登入來檢驗您的新頁面,並且觀察當角色改變時頁面的內容如何改變。請注意,如果兩個角色群組都與使用者的角色相匹配,則總是顯示第一個匹配的角色群組(從上到下)。
雖然這並不新鮮,但是請您記住,您始終可以通過User.IsInRole以編程方式測試角色。還需要謹記的是,您可以使用web.config中的部分來准許或拒絕訪問各個頁面,如下所示:
<authorization>
<deny users='?'/>
<allow roles='ForumModerators'/>
<deny users='*'/>
</authorization>
第一項告訴ASP.NET禁止傳入任何未經身分識別驗證的請求(強制執行身分識別驗證)。第二項和第三項確保只有ForumModerators可以訪問web.config檔案所駐留的分類樹中的內容。記住,授權部分可用於子目錄中的web.config檔案,也可以用於元素,以控制對單獨檔案的訪問。
PasswordRecovery密碼恢複控制項
該控制項可以實現簡單地密碼提示恢複功能,並將密碼email給使用者,代碼如下:
| <asp:PasswordRecovery id="prForgotPass" runat="server"> </asp:PasswordRecovery> |
實現時如所示:
使用者輸入使用者名稱後,則需要輸入密碼恢複的答案,如:
並在該控制項中設定將恢複後的密碼EMAIL給使用者
<asp:PasswordRecovery id="prForgotPass" runat="server"> <MailDefinition BodyFileName="forgotpassword.txt" From="helpdesk@dorknozzle.com" Subject="Word has it, you forgot your password?"/> </asp:PasswordRecovery> |
ChangePassword 更改密碼控制項
更改密碼控制項可以很方便地實現密碼的更改,包括一個 MailDefinition 屬性。如果將值賦給 MailDefinition 屬性,則 ChangePassword 控制項在密碼成功更改時將自動向使用者發送一個電子郵件。
直接使用Membership API
有時候,需要對成員資格進行更進階別(比 Web 網站管理工具或 Login 控制項提供的控制層級高)的控制。在這些情況下,可以直接使用 Membership API。
Membership API 是通過 Membership 類公開的。Membership 類包含的方法使您能夠完成以下工作:建立新使用者;更改密碼;搜尋與特定條件匹配的使用者等等。在幕後,Login 控制項使用這些方法與已配置的成員資格提供者進行互動。
下面列出了 Membership 類的一些比較重要的方法:
CreateUser — 使您能夠建立新使用者。
DeleteUser — 使您能夠刪除現有使用者。
FindUsersByEmail — 使您能夠檢索與某個特定的電子郵件地址匹配的使用者集合。
FindUsersByName — 使您能夠檢索與某個特定的使用者名稱匹配的使用者集合。
GeneratePassword — 使您能夠產生隨機密碼。
GetAllUsers — 使您能夠檢索成員資格提供者中儲存的所有使用者。
GetNumberOfUsersOnline — 使您能夠返回當前正在訪問 Web 應用程式的使用者數。
GetUser — 使您能夠檢索與目前使用者相關聯的成員資格資訊,或者使您能夠檢索與某個已經提供使用者名稱的使用者相關聯的成員資格資訊。
GetUsernameByEmail — 使您能夠檢索具有某個特定電子郵件地址的使用者的使用者名稱。
UpdateUser — 使您能夠更新特定使用者的資訊。
ValidateUser — 使您能夠根據成員資格提供者對使用者進行身分識別驗證。
最後,我們利用該API,實現稍微複雜的功能。在index.aspx中,添加一個label控制項,顯示線上的人數,代碼如下:
Number of Users Online: <asp:Label id="lblNumUsersOnline" runat="server"/> |
在PAGE-LOAD事件中,增加代碼如下:
Sub Page_Load(s As Object, e As EventArgs) Handles MyBase.Load lblNumUsersOnline.Text = _ Membership.GetNumberOfUsersOnline().ToString() End Sub |
則可以顯示線上人數。我們並且再添加一個gridview,顯示所有使用者的線上狀況,是否允許某種許可權和相關的資訊。
<asp:GridView id="gvUsers" runat="server" AutoGenerateColumns="False"> <Columns> <asp:BoundField HeaderText="Username" DataField="Username" /> <asp:BoundField HeaderText="Is Online?" DataField="IsOnline" /> <asp:BoundField HeaderText="Is Approved?" DataField="IsApproved" /> <asp:BoundField HeaderText="Email" DataField="Email" /> </Columns> </asp:GridView> |
並將gridview綁定:
gvUsers.DataSource = Membership.GetAllUsers() ; gvUsers.DataBind(); |
最後運行結果如:
總結:
本文以實際例子,介紹了asp.net 2.0中新增加的登陸系列控制項的簡單用法以及新增加的Membership API的簡單使用,更多的請參考MSDN或正式版時的有關介紹。