2012年5月31日 星期四

ASP.Net 使用 Form 認證設定使用者登入資訊

在 ASP.Net 專案,如果使用的認証方式為 Form 認證,當確認了登入者身份後, 會將使用者是否已經經過認證的訊息寫到 Cookie裡,而 Cookie 名稱為 ASPXAUTH。透過以下方法,可以看到 Cookie 的值。

Request.Cookies[“ASPXAUTH”].Value

或是用下面這段迴圈去抓出目前的 Cookie 資訊:
StringBuilder sb = new StringBuilder();
        
for (int i = 0; i < Request.Cookies.Count; i++)
{
     sb.Append(string.Format("<br>Name:{0} Values:{1}", Request.Cookies[i].Name,Request.Cookies[i].Value));
}

lb_Cokies_info.Text=sb.ToString();

當然,你所看到的 Cookie 值是經過編碼加密後的一串文字。
剛剛提到,確認並通過了使用者身份後,會將資訊寫到 Cookie 裡,那這寫入的動作,是誰做?不用想太多,當然是程式設計師自己去做啦~

介紹目前自己常常看到的三種寫入認證 Cookie 方式:

一、RedirectFromLoginPage()
舉例來說,假設使用者 paladin 填寫完帳號密碼,並檢查無誤後,就可以用下面語法將 “paladin” 這帳號訊息寫到 ASPXAUTH Cookie裡。所以RedirectFromLoginPage 方法裡的第一個參數,是用來存放被認可的使用者帳號資訊。

第二個參數還蠻值得玩味的,官方說法是表示建立持久性 Cookie (跨瀏覽器工作階段儲存的 Cookie) (Ref.http://msdn.microsoft.com/zh-tw/library/bk50ykcd.aspx) )。經實際測試後,所謂的跨瀏覽器並不是跨 IE、Chrome、Firefox,而是指是否允許在另開 IE 瀏覽器時使用同一份 Cookie,所以IE 裡的 ASPXAUTH Cookie 與 Chrome 或 Firefox 都不一樣,他們都有各自的 Cookie。但如果你是使用開啟新索引標籤的方式,則不管值為何,都是使用同一份 Cookie,每一個標籤都抓的到。

此外,當第二個參數設為 true 時,預設 Cookie 的存活時間約 30 分鐘,也就是說,當你把所有瀏覽器都關閉時,在30分鐘內都還可以存取到有效的 Cookie 值。但如果參數設為 false,則當視窗都關閉後,Cookie也就會自動失效。
using System.Web.Security;

FormsAuthentication.RedirectFromLoginPage(“paladin”, false);

此外,使用RedirectFromLoginPage()方法時,顧名思義當他執行完後,應該會把頁面轉到(Redirect)其他頁面去吧,實際上也真是如此。當執行完後,他會先檢查目前的 URL 裡是否有設定 ReturnUrl,如果有,就會將頁面轉到  ReturnUrl 所定義的頁面。舉例來說,如果你的驗證登入的頁面是:



http://paladin.love.girl.tw/login.aspx?ReturnURL=hello_baby.aspx

當執行 RedirectFromLoginPage() 方法後,你的頁面就會轉到

http://paladin.love.girl.tw/hello_baby.aspx



但如果你忘了或不想在 URL 上面指定 ReturnURL 參數,那也沒關係,他會根據目前 FormsAuthentication.DefaultUrl 所定義的值來決定歸處,但預設值是 default.aspx。這裡需要注意的是,FormsAuthentication.DefaultUrl 只能取得他的值,卻不可直接設定他,如果真要改變的話,需在 web.config 裡的 defaultUrl 調整(Ref.FormsAuthentication.DefaultUrl 屬性)。


<authentication mode="Forms">
  <forms loginUrl="member_login.aspx"
    defaultUrl="index.aspx" />
</authentication>

二、SetAuthCookie()
如果對於 ReturnURL 還耿耿於懷,或是希望自己能夠完全主導認證後頁面轉向的控制權,則可以使用 SetAuthCookie() 方法來完成。他的使用方式跟前面的 RedirectFromLoginPage() 非常相似,舉例來說:


FormsAuthentication.SetAuthCookie(“paladin”, false);

第一個參數是將登入者的帳號寫到 ASPXAUTH Cookie 裡,第二個參數也跟RedirectFromLoginPage() 一樣,用來表示是否建立持久性 Cookie。最大的不同,在於SetAuthCookie() 執行完後,不會將你目前的頁面帶離,而是留給程式設計師自行決定。

三、FormsAuthenticationTicket
前面兩種作法,都算是輕巧簡便型,他們把很多複雜的設定都封裝起來,所以用起來很方便,但從另一個角度來說,也就失去許多原本可以設定的選項,變得比較沒有彈性。如果希望自己撰寫個比較有彈性的寫法,可以利用 FormsAuthenticationTicket 類別來實作。以一段程式來說明:

      bool isPersistent = false;

        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
            1,
            "paladin",
            DateTime.Now,
            DateTime.Now.AddMinutes(30),
            isPersistent,
            "Admin",
            FormsAuthentication.FormsCookiePath
            );

        // Encrypt the ticket.
        string encTicket = FormsAuthentication.Encrypt(ticket);


        HttpCookie authenticationCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);

        //將HttpCookie是否永久存在的屬性與FormsAuthenticationTicket綁在一起
        if (isPersistent)
            authenticationCookie.Expires = ticket.Expiration;

        // Create the cookie.
        Response.Cookies.Add(authenticationCookie);


首先建立了一個 FormsAuthenticationTicket 類別,建構式裡有7個參數可以設定。
1.設定 ticket 的版本
2.設定要存放的使用者帳號
3.設定 ticket 產生的時間
4.設定 ticket 過期時間
5.設定這個 ticket 是否是持續的
6.允許使用者任意輸入的文字(但不是無限制長度,Cookie 能保存的文字長度有限制)
7.Cookie 存放路徑

這裡的參數設定,主要針對第六個參數要特別注意。通常我們確認了使用者身份,且將使用者的帳號記錄下來後,還有可能會進一步去將他的群組資料也放進去。例如使用者是「Admin」還是「Guest」,或是同時具備多種群組身份。第六個參數允許你存放的是字串格式,可以拿來存放群組資訊。如果準備存放多種群組角色時,一般都可以使用 | 或其他分隔字元來區別,例如:Admin|Guest 。但當你在使用時,需特別注意,因為 Cookie 的長度並非無限制,如果將太多的資訊(例如:分機、住址...)都全部往裡面寫,那就有可能會造成錯誤,所以盡可能寫比較重要且精簡的內容,過多的相關資訊,可以考慮存放索引值就好,需要用到時再去資料庫反查。


最後,我們可以透過以下方式取得先前所存放的使用者資訊:





//取得使用者帳號
        if (User.Identity.IsAuthenticated)
        {
            lbUserInfo.Text = string.Format("目前登入者帳號為:{0}", User.Identity.Name);
        }
        else
        {
            lbUserInfo.Text = "目前尚未登入";
        }

//取得使用者自訂資料
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (authCookie == null) return;
FormsAuthenticationTicket authTicket =FormsAuthentication.Decrypt(authCookie.Value);
Response.Write(authTicket.UserData);



以上一切的一切,都是假設您是使用 Form 認證才會成立的,所以在 web.config 中,必須確認有以下的設定喔。
<authentication mode="Forms">

參考:
01.概略解釋 Forms Authentication 的運作

2012年5月27日 星期日

一念之間的差異

盧智芳於 Cheers 提到,她在國三畢業典禮前夕,校長到每個班級對畢業生講話,他一開始什麼都沒說,只在黑板上畫了一個簡單的圖,然後說:在人生中,畢業典禮就像這個角的頂點,一開始,每個人的出發點都一樣;但是隨著時間的推移,有一個因素會讓彼此的差異愈來愈大,愈來愈大。這個因素是什麼?就是一個人的觀念。要是觀念一開始是錯的,以後就全錯了。

記得禪聞法師也曾提到:

不對的方法,做再多,都是錯,浪費時間。
對的方法,慢慢做,才會進步,不見得時間會花比較多。

所以,一開始能夠找到正確的觀念,正確的方法,是決定你圓滿的關鍵。這裡強調的是圓滿,而不是成功。因為成功有可能只是表象,例如你成功獲得了選舉,卻使用了見不得人的手段,雖說你成功了,但卻不圓滿。

侯吉諒於其「一念之間」文章中有提過:

「人很容易受到環境的影響,因此需要一定程度的內在修養,才能不受外來惡戾之氣的影響,能知不足,則不敢不努力,能以平常心看得破一時際遇的興衰,則世間榮辱可以不驚不畏,能無所住而生其心,則不會有偏見,能以善待人,於是物物大好,事事好極了,不必求福而自然萬得福。」


雖說是外來惡戾之氣的影響,但說到底,還是自己內心調伏的問題。會左右我們選擇的因素,常常是因為「我無甲意輸的感覺!(台語)」。因為害怕,所以採取了許多明知不可為而為的作法,最後反而輸的更慘,付出更大的代價。


在強調運動精神的公平競賽中,競爭結果的輸贏並不是壞事。它訂出公平的標準,讓參賽者分出高下。贏的人很清楚知道哪一方面的表現,適合自己發揮長才;輸的人也可以藉此檢討自己努力不夠、或是方法有問題,還是根本就是把自己放錯了位置,不該參加這個賽局。

如果不能接受自己落在輸局的處境,進而透過檢討反省找到新的方向,就會讓自己執著在「輸」的負面感覺裡,變得憤怒而失去理性。

在該篇文章中,有幾個很棒的觀點,節錄於後:

  1. 願意成全別人,是更實際的贏家
    分享一個精彩的故事:
    左宗棠,並非天生贏家。他也有過輸的紀錄。據說,非常擅長下圍棋的他,曾經在一次出兵前的空檔,路過一個號稱「天下第一」的棋王住處,於是他主動下馬挑戰,當場贏了三盤棋,爽快地離開趕赴戰場之前,還特別糗了一下主人「天下第一」的封號。

    為朝廷打完勝仗,班師回朝途中,左宗棠又再度路過棋王住處,意猶未盡地進去下棋,想要再度衛冕成功,沒想到這一回卻連輸三盤。棋王微笑地說:「上一次你來找我下棋的時候,正好是要率兵出征,我不能讓你因為輸棋而挫失銳氣。現在你打勝仗回來,我就當仁不讓了。」

    棋盤上的輸贏,也許有真有假。人生裡的輸贏,也未必就此見真章。但只要擁有足夠的自信,能夠體察別人的需要,願意成全別人,即使表面上輸了,並不是真的輸,反而可能是更實際的贏家。棋王,贏了友誼,也贏了自己。
  2. 輸贏不是跟別人的比較,而是對自我的挑戰
    戰勝自己一直想「贏過別人」的念頭,是層次更高的一種贏法。
  3. 輸贏不是單線的,而是多面的
    學業或事業很成功的人,可能忽略了人際關係或家庭幸福。評估輸贏時,別忘了各個面向的均衡。
  4. 輸贏不是表面的,而是一體的
    輸贏是一體兩面的,贏到某些東西的時候,可能正失去其他的東西。


參考:
01: Cheers 130期,一念之間的差異
02:侯吉諒:一念之間
03:面對自我挑戰-輸與贏在一念之間(每週一讀)

2012年5月22日 星期二

.Net 1.1 的有痛升級

於 .net 1.1 ,專案名稱 UpTestWeb ,預設在 /bin 資料夾就會產生 UpTestWeb.dll。當要升級為 .net 2.0 以上時,一般都習慣使用升級精靈來自動將專案轉換為新的 .net 版本。目前我則是將 .net 1.1 升級為 .net 4.0,升級完成後,一開始執行都沒甚麼大問題,但當有需要修改程式時,才發現原先自己定義的一些類別,不管怎麼改,程式都不會去執行剛剛修改的地方,還是照著他自己的舊寫法去跑,真的還蠻奇怪的。

理論上,於 .net 4.0 時,/bin 資料夾並不會出現跟專案名稱一樣的 dll 檔,但檢視我升級後的專案,裡頭卻有一個 UpTestWeb.dll ,於是我把他砍了,接著一堆錯誤就出來了,眼睛很花,頭...很沈重。

在網路上看到許多前輩討論 .net 1.1 升級的經驗談,其中於 shang 的一篇文章「 从ASP.NET 1.1升级到2.0 」提到:「将所有独立的代码文件和AssemblyInfo.cs都被移到 App_Code 目录下」,發現自己少了這個步驟。原來,如果將與 .aspx 無關的 .cs 都移到 /App_Code ,於 .net 2.0 以後才可以讓其他程式碼存取。基本上,我們自己所定義的類別程式,都應該放在 /App_Code,若是已經編譯過的 dll 檔,則是擺放在 /Bin 下面,如此其他程式才能參考的到(Ref.ASP.NET 網站中的共用程式碼資料夾)。

在這次升級的過程中,我大致做了以下動作:

01:將與 .aspx 無關的 .cs 都移到 /App_Code
02:把 .aspx 裡的Codebehind 改成 CodeFile,同時也要調整 Inherits。舉例來說:
<%@ Page language="c#" Codebehind="Default.aspx.cs" AutoEventWireup="false" Inherits="UpTestWeb._Default" %>

換成

<%@ Page language="c#" CodeFile="Default.aspx.cs" AutoEventWireup="false" Inherits="_Default" %>

03:把 .aspx 的
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

換成

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

04:將 .aspx.cs 裡的 namespace XXX { ... } 拿掉, 並將
public class xxx : System.Web.UI.Page 改成
public partial class xxx : System.Web.UI.Page

05:於 .aspx.cs 裡,類似 protected System.Web.UI.WebControls.Label lblUserName; 這種的宣告都砍掉。

參考:


01:ASP.NET 網站中的共用程式碼資料夾

02:A Beginner's Guide to ASP.NET Application Folders

03:从ASP.NET 1.1升级到2.0

04:KB-ASP.NET 2.0 網站部署的變革