最近由於項目代碼重構須要從新整理用戶登陸和權限控制的部分,現有的代碼大致是參照了.NET的FORM認證,並結合了PORTAL KITS的登陸控制,代碼比較囉嗦,可維護性比較差。因而有了如下的幾個需求(大多數系統應該都會碰到):程序員
第一步:準備工做web
先準備一個名爲Test的WEB項目,包含:數組
Default.aspx,默認頁,隨便顯示一些信息,瀏覽器
Login.aspx,登陸頁,上面放兩個文本框,用來輸入用戶名和密碼,一個登陸按鈕,一個指向Register.aspx的超鏈,安全
Register.aspx,用戶註冊頁,註冊用戶信息,隨便放一點文本框,主要是模擬一下注冊,不用真正實現,服務器
Web.config,配置頁面。cookie
註冊頁與登陸頁在同一目錄的機妙後面會說。app
第二步:Web.config文件的修改ide
一、打開Web.config文件,找到authentication節,將其改成以下: 函數
配置節屬性的具體意義和其餘沒有加入的屬性網上處處都有查。這裏注意一下的是authentication節和authorization節,兩個單詞很類似,但卻不是同一個單詞,每一個節下面的內容也不能寫到一塊兒。
其中,authorization節中的「allow」表示容許的意思,「*」表示全部用戶;而「deny」表示拒絕的意思;「?」表示匿名用戶;此處加入後,則表明根目錄下的全部文件和全部的子目錄都不能匿名訪問,Login.aspx 頁面除外。
二、Web.config中,Location節的應用
作了上面的配置以後,咱們會發現,在沒登陸的狀況下,用瀏覽器打開Default.aspx會自動轉到Login.aspx,同理Register.aspx頁面也會如此。問題:註冊用戶怎麼可能在登陸後才能訪問呢?
那麼咱們就得說了,當註冊頁與登陸頁在同一目錄,爲了達到不用登陸就能訪問註冊頁的目的,咱們就得對訪問限制的Web.config配置處理一下。
方法一 :註冊頁與登陸頁放在不的目錄內
咱們在根目錄添加一個文件夾Pub,將Register.aspx移動到此文件夾裏,此時仍不能訪問,須要在文件夾內添加一個Web.config文件,加入:
此處,即說明此目錄下的全部文件,容許全部人訪問。
關於 Web.config 做用範圍的說明:
方法二:仍然保持註冊頁和登陸頁在同一目錄下
只須要在根目錄下的Web.config 中加入如下一段:
經過location節的path屬性的值指定Register.aspx頁面,以及下面authorization節的設置,說明了Register.aspx頁面是容許被全部人訪問。
注意:
location節應加在原有的system.web節的外面,包含在configuration節內,和system.web節是同級的。
當根目錄下,有多個頁面不須要登陸就能夠訪問時,能夠設置多個location節,修改對應path屬性值指向的頁面就能夠了。
另外,path屬性的值也能夠指定目錄,用來指定該目錄的訪問限制。經過修改authorization節的內容來限定訪問權限。詳細的設置,後面會提到。
第三步:實現登陸的代碼
一、普通的代碼實現
方法一:
若是forms節中設置了「defaultUrl」的屬性,也就是登陸後默認轉向的頁面,則能夠用以下的方法:
此處只是簡單模擬了一下登陸的驗證過程,RedirectFromLoginPage方法能發送驗證票據驗證Cookie(如何進行能夠用Reflector去查看源代碼),返回請求頁面,即「從哪來就打哪去」。好比:用戶沒登陸前直接在 IE 地址欄輸入 http://localhost/Test/Default.aspx ,那麼該用戶將看到的是 Login.aspx?ReturnUrl=Default.aspx ,輸入用戶名與密碼登陸成功後,系統將根據「ReturnUrl」的值,返回相應的頁面;若是沒有「ReturnUrl」,則按照「defaultUrl」的屬性自動轉向。
方法二:
此處是分兩步走:經過驗證後就直接發放 Cookie ,跳轉頁面將由程序員自行指定,無需「defaultUrl」設置。此方法對於程序員來講,更靈活。
二、手工實現須要記錄用戶登陸信息的狀況
當咱們須要記錄用戶登陸的信息,不僅僅只是一個ID還須要更多屬性的時候,通常都用一個類存儲到Session或Cookie實現,而後作一個基類頁,在基類頁中設置屬性來讀取Session或Cookie。
Session實際也和支不支持Cookie有關,且存在服務器,多少會佔用服務器端資源。所以這裏仍是考慮用Cookie實現。那麼在RedirectFromLoginPage方法或SetAuthCookie方法已經設置了驗證票據並設置了Cookie,咱們能不能把用戶登陸信息也存儲到這個默認的Cookie裏呢?答案是能。
首先,咱們在項目裏添加AppCode目錄,增長一個UserInfo的類,用以簡單模擬用戶登陸信息。代碼以下:
須要注意, 類的屬性中必定要加[Serializable],表示類能夠序列化。
Forms驗證在內部的機制是,把用戶數據加密後保存在一個基於cookie的票據FormsAuthenticationTicket中,經過RedirectFromLoginPage方法或SetAuthCookie方法就已經實現了Ticket和Cookie的設置,也就是設置了Context.User的值,Context.User在取值和判斷是否通過驗證的時候頗有用處。Cookie的屬性是在Web.config的<forms name=".ASPXAUTH" loginUrl="Login.aspx" protection="All" path="/" timeout="20"/>中設置的。由於是通過特殊加密的,因此應該來講是比較安全的。
而.net除了用這個票據存放本身的信息外,還留了一個地給用戶自由支配,這就是 如今要說的Ticket的UserData。 UserData用來存儲string類型的信息,而且也享受Forms驗證提供的加密保護,當咱們須要這些信息時,也能夠經過簡單的Ticket的 UserData屬性獲得,兼顧了安全性和易用性,用來保存一些必須的敏感信息仍是頗有用的。咱們就準備將用戶的登陸信息記錄在UserData中,代碼以下:
上面的代碼,實際上相似於手工實現了SetAuthCookie方法的過程。
首先,模擬實現登陸,咱們手動設置了一個UserInfo的對象,string strUser = Serialize.Encrypt<UserInfo>(user) 是將對象序列化成字符串的一個方法。
而後,生成一個FormsAuthenticationTicket票據。此處用到的FormsAuthenticationTicket構造函數的重載方法的簽名解釋
其中,name的設置與Context.User.Identity.Name對應,且大小寫敏感,也與未來的權限控制相關,賦值的時候須要特別注意。另外,票據的到期日期和Web.config中設置的Cookie的到期日期不是同一個概念,若是分不清,請到網上去搜索,若是實在不想在這上下功夫,後面會有處理的方法。
再後,string strTicket = FormsAuthentication.Encrypt(ticket) 將票據加密成字符創
最後,HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strTicket) 生成Cookie。
FormsAuthentication.FormsCookieName獲取的就是Web.config中配置的Cookie名稱,也就是默認驗證時產生的Cookie。cookie.Expires = ticket.Expiration 將票據的過時時間和Cookie的過時時間作了同步,也就避免了二者不一樣所產生的矛盾。這樣,驗證票據生成了,存儲到默認配置的Cookie中,也就是相似實現了一個SetAuthCookie方法的過程。經過Context.User就能獲取票據的相關信息了。
三、獲取信息
爲了在其餘登陸後的頁面比較簡單的獲取登陸用戶信息,咱們先生成一個基類頁面。在AppCode中新增LoginBasePage類,代碼以下:
LoginBasePage : Page,基類頁要繼承Page,成爲全部登陸之後的頁面的基類。
屬性protected UserInfo LoginUser{ get;}用來訪問登陸信息。將Context.User.Identity強制轉換爲FormsIdentity類的對象,經過訪問Ticket屬性的UserData屬性,得到被序列化後的對象的字符串,最後用方法Serialize.Decrypt<UserInfo>(strUser)將字符串反序列化成對象後再返回UserInfo類型的對象。
咱們只須要將Default頁面的後臺代碼改成public partial class _Default : LoginBasePage,就能夠經過this.LoginUser來訪問用戶登陸信息了。
第四步:實現不一樣目錄的權限控制
上面,實現了記錄用戶登陸信息的模擬登陸過程,以及根目錄下文件的訪問控制。可是系統通常都會有多個目錄,接下來就說說目錄的訪問控制。
其實,上面多多少少已經提到過了,經過在每一個目錄下增長Web.config文件來進行訪問限制。
首先,咱們在根目錄增長一個文件夾ManageAdmin,在此文件夾內增長頁面UserInfo.aspx,頁面內放幾個Label用來展示登陸用戶信息。
而後,再增長一個Web.config文件,配置內容以下:
配置中說明只容許「Admin」用戶訪問,禁止其餘全部用戶訪問。
這裏,特別要注意的是,FormsAuthenticationTicket票據的name屬性的賦值,必定要和<allow users="Admin"></allow>設置的用戶想對應,且大小寫敏感。若是要設置容許多個用戶訪問,則用「,」隔開,例如<allow users="Admin,User1"></allow>。
不一樣的目錄,設置不一樣的容許訪問的用戶,就能夠對全部目錄進行訪問控制了。
第五步:實現不一樣目錄的按角色的權限控制
以上實現了對不一樣目錄按用戶的訪問限制。可是通常來講,一個網站系統的用戶會不少,若是一直使用精確到用戶的訪問控制,則會形成設置Web.config的工做量加大。
而通常,咱們會將用戶分到不一樣的用戶組來進行權限控制,所以,咱們也能夠配置Web.config實現按角色來控制不一樣的目錄的訪問權限。
首先,咱們在根目錄下再增長一個目錄ManageUsers,在此文件夾內也增長頁面UserInfo.aspx用來展示登陸用戶信息。此目錄將模擬控制Users組的用戶,文件夾ManageAdmin將模擬控制Administrators組的用戶。
而後,在目錄ManageUsers增長Web.config文件,配置內容以下:
再將文件夾ManageAdmin下的Web.config文件的<allow users="Admin"></allow>改爲<allow roles="Administrators"></allow>。
最後,修改代碼。
一、注意,咱們在模擬用戶信息的時候,有這麼一句,user.Roles = "Administrators,Users";也就是用戶Admin具有兩種角色
二、爲模擬Users組的用戶登陸,咱們再添加以下代碼:
這樣,咱們登陸時,輸入「Admin」和「User1」的時候,就能夠模擬不一樣角色的用戶登陸了。
三、Forms基於角色的驗證的內部機制是,將角色的屬性也設置到了Context.User中,這裏也須要手工代碼處理一下。
首先,爲了支持基於角色的驗證,咱們每進入一個頁面都須要將角色信息設置到Context.User中,那麼最好的辦法就是在Global.asax 文件中的Application_AuthenticateRequest方法中設置。
Application_AuthenticateRequest方法,是在每次驗證請求時觸發,它與另一個方法 Application_BeginRequest的區別就在於,Application_AuthenticateRequest方法內,可以訪問 Context.User.Identity,而Application_BeginRequest則沒法訪問。
咱們在根目錄添加一個Global.asax 文件,增長以下代碼:
此處,主要代碼就是將Context.User.Identity強制轉換爲FormsIdentity類的對象,經過訪問Ticket屬性的UserData屬性,得到被序列化後的對象的字符串,最後用方法Serialize.Decrypt<UserInfo>(strUser)將字符串反序列化成對象,再將UserInfo對象的Roles屬性以「,」爲分隔符分隔成角色數組,再用Context.User.Identity和角色數組生成一個新的GenericPrincipal對象,賦值給Context.User ,則Context.User 爲已經設置好角色的驗證對象。
按照咱們的設置,Admin用戶能訪問兩個目錄,而User1用戶,則只能訪問ManageUsers一個目錄。
第五步:集中管理Web.config文件
目錄的訪問權限控制,是按用戶仍是按角色,通常由具體業務決定。
可是,隨着目錄的增多,每一個目錄下都存在一個Web.config文件,管理起來特別不方便。
經過上面提到過的location節的path屬性,咱們能夠實現Web.config配置的統一管理。咱們能夠將各個文件或目錄的配置都放置在根目錄的Web.config文件內,代碼以下:
結尾:
此次完全理順FORM驗證的過程,發現了很多實用性很強的技巧,中間參考了不少網友的文章,也經過Reflector看了一下具體實現的源代碼。感受收穫很多,最大的收穫就是對於問題不但要知其然更要知其因此然,要有一種打破沙鍋問到底的淨勝。
你們若是有什麼問題有什麼疑問,不但要找到解決的辦法,有時間的話最好從理論到底層代碼都好好過一過,對本身的水平長進有很大的幫助。
這篇文章還有不少方面沒有涉及,也有不少高深的東西沒有講到,例如「經過PrincipalPermissionAttribute配合Forms驗證進行基於角色或用戶的安全驗證」。就當留給本身一個研究的尾巴吧。