說明:兩個月前我剛學(xué) asp.net, 在 codeproject.com 看到題目叫 role-based security with forms authentication 的文章,覺得很有幫助。當(dāng)時就想翻譯成中文。不過直接翻譯實在沒意思,這兩天我參照 heath stewart的這篇文章,并且根據(jù)自己的理解,把它按照自己的想法和表達方式寫成中文。附帶上自己為這篇文章做的一個演示的web應(yīng)用程序。
如果有理解錯誤的地方,歡迎來信指出或發(fā)表評論。
概要:
asp.net 提供了基于角色(即 roles)的認證機制,然而它對角色的支持是不完全的。本文試圖通過一些例子來說明如何實現(xiàn)和使用這種基于角色的認證機制。
簡介:
asp.net 中窗體認證是一個功能非常強大的特性,只需要很少的代碼就可以實現(xiàn)一個簡單的平臺無關(guān)的安全認證系統(tǒng)。
但是,如果你需要一個更復(fù)雜更有效的認證機制,那么你就要把眾多用戶分成用戶群組,以利用它的靈活性。windows 集成認證提供了這種認證機制,但它使用的是 ntlm,即windows nt lan manager,因而它不是跨平臺的。現(xiàn)在越來越多的人使用 linux 系統(tǒng),而 mozilla forefox 瀏覽器用戶也越來越多,我們肯定不能把這些人拒之門外,因此我們尋求另外的認證機制。有兩個選擇:一是為網(wǎng)站劃分多個區(qū)域,提供多個登錄頁面,強迫用戶一個一個的去注冊和登錄;二是把用戶分組,并且限制特定用戶組對某頁面或者某區(qū)域訪問的權(quán)限。后者當(dāng)然是更好的選擇。通過分配角色給各個用戶,我們能夠?qū)崿F(xiàn)這種功能。
微軟為.net平臺留下了窗體認證中基于角色的認證機制,但是我們必須自己去實現(xiàn)它。本文力求覆蓋窗體認證中基于角色的認證機制的一些基本的東西,比如它的概念,它的實現(xiàn),如何在web應(yīng)用程序中應(yīng)用等。
必要準備:
我們首先要建立一個數(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 文件中是大小寫敏感的。現(xiàn)在我們?yōu)閷崿F(xià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放在哪里是無所謂的,但是它必須是公眾有權(quán)限訪問的。
在應(yīng)用程序根路徑下,我們創(chuàng)建兩個機密的子目錄,分別是 admin 和 user。
接下來,我們創(chuàng)建一個支持角色認證的窗體認證登錄系統(tǒng)。因為微軟沒有提供簡單的實現(xiàn)機制,我們要自己花些時間去創(chuàng)建認證票據(jù)。它需要存貯少量信息,當(dāng)然,有些名稱必須和 web.config 中配置的一樣,要不asp.net 就會認為你的認證票據(jù)是無效的,從而強制轉(zhuǎn)向到登錄頁面。我們在 vs.net 中為 login.aspx 添加兩個textbox控件,取名 usernametextbox, passwordtextbox,再添加一個button,取名 loginbutton,點擊它進入后臺代碼。在 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 [email protected] " +
"and [email protected]";
// 填充各個參數(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)認證,創(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, // 認證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>
你會注意到上面我們對密碼的處理:將它哈希加密。哈希加密是一種單向算法(不可逆算法),生成唯一的字符數(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ù))。先要確認已經(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);
}
}
}
}
認證票據(jù)(用戶名和密碼)是沒有作為cookie的一部分來存儲的,而且也不可以,因為用戶可以修改他們的cookie。
事實上,formsauthentication是用你的機器碼 (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)安全認證,在 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)站配置了基于角色的安全認證機制了。你可以先編譯你的程序,然后嘗試訪問一個機密目錄,例如 http://localhost/rolebasedauth/admin ,這時候你就會被轉(zhuǎn)向到用戶登錄頁面。如果你登錄成功,并且你的角色對這個目錄有訪問權(quán)限,你就重新回到這個目錄下。可能會有用戶(或入侵者)企圖進入機密目錄,我們可以使用一個 session 來存儲用戶登錄的次數(shù),超過一定次數(shù)就不讓用戶登錄,并且顯示"系統(tǒng)拒絕了你的登錄請求!"。
下面,我們討論如何根據(jù)用戶角色讓web控件顯示不同內(nèi)容。
有時候根據(jù)用戶的角色來顯示內(nèi)容比較好,因為你可能不想為那么多不同的角色(用戶群組)制作一大堆有許多重復(fù)內(nèi)容的頁面。這樣的網(wǎng)站,各種用戶帳戶可以并存,付費的用戶帳戶能夠訪問附加的付費內(nèi)容。另一個例子是一個頁面將顯示一個 "進入后臺管理" 按鈕鏈接到后臺管理頁面如果當(dāng)前用戶是 "administrator"(高級管理員)角色。我們現(xiàn)在就實現(xiàn)這個頁面。
我們上面用到的 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 = "管理員請進";
adminlink.navigateurl="./admin";
}
else if(user.isinrole("user"))
{
adminlink.text = "注冊用戶請進";
adminlink.navigateurl="./user";
}
else
{
adminlink.text = "請登錄";
adminlink.navigateurl="login.aspx?returnurl=" + request.path;
}
}
這里,我們通過設(shè)置叫做returnurl的 querystring 變量,可以使用戶登錄成功后返回到當(dāng)前的這個頁面.
小結(jié):
本文用于幫助你理解基于角色安全機制的重要性、實用性,并且也用 asp.net 實現(xiàn)了基于角色的安全機制。它并不是一個很難實現(xiàn)的機制,不過它可能需要一些相關(guān)知識如 什么是用戶憑證,如何認證用戶身份,以及如何審定授權(quán)用戶。如果你覺得它很有幫助,我會非常高興。我希望它可以引導(dǎo)你在你的網(wǎng)站中去實現(xiàn)基于角色的窗體安全認證機制。
新聞熱點
疑難解答
圖片精選