Asp.Net 用戶驗證(自定義IPrincipal和IIdentity)

作者: Jimmy Zhang  來源: 博客園  發布時間: 2008-12-14 20:32  閱讀: 2932 次  推薦: 0   原文鏈接   [收藏]  

源碼下載:http://www.tracefact.net/SourceCode/FormsAuthentication.rar

引言

前 一段時間有兩個朋友問我,為什么在HttpModule中無法獲得到Session值,因為他們希望自定義一個HttpModule,然后在其中獲取 Session來進行用戶驗證。我奇怪為什么不使用.Net Framework已經提供的驗證機制,而要和Asp時一樣,自己手工進行cookie+Session驗證?我們是基于.Net Framework這個平臺進行編程,所以我覺得,在很多情況下,使用Framework已經建立好的機制會顯著地提高工作效率,而且.NET Framework內置的驗證機制通常也更加安全。

.Net提供了一整套的驗證和授權機制,這里驗證和授權是不同的概念,驗證 (Authentication)是指“證明你確實是你所說的人”,通常是提供一個用戶名和口令,然后與持久存儲(比如數據庫)中的用戶名和口令進行對 比。授權(Authorization)是指“你是否有足夠的權限做某件事”,此時你的身份已經被證明過了(匿名用戶、會員還是管理員),授權通常與用戶 組或者用戶級別聯系起來,不同的用戶組擁有不同的權限(訪問特定頁面或者執行特定操作)。

回想一下我剛接觸.Net時,也曾經完全繞過.NET的驗證,自己編碼采用Cookie+Session實現身份驗證,并且一個Asp.Net 登錄控件都沒有使用,那時候的理由是:我要使用自定義的用戶表,不能使用Asp.Net安全機制在App_Data下自動生成的AspNetDB.mdf中的一系列數據表。除此以外,還有一個原因,就是.Net 驗證機制的核心IPrincipal和Identity提供的信息用戶信息太少了,當在頁面后置代碼中使用繼承來的User屬性(IPrincipal類 型)時,它的Identity屬性只有一個Name與用戶數據相關(AuthenticationType與IsAuthenticated都是與驗證相 關),而很多時候我們都需要許多額外的用戶數據。其實這只是一個誤解罷了,以為使用Asp.Net的驗證機制和登錄控件就一定要使用其附帶的數據表,以為Identity就只能攜帶一個Name屬性。

實 際上,.NET的安全機制包括了幾個部分,除了驗證以外,還包括MemberShip、Profile、Role等,我們完全可以只使用它的驗證機制,而 繞過它的MemberShip、Profile和Role,來實現通常我們用Cookie+Session完成的功能,而且更高效更安全。這篇文章將快速 地實現這樣的一個流程。

開始前的準備

創建頁面,配置Web.config

我們先創建解決方案、建立站點,然后在站點中添加下述文件,它們將會在后面使用:

接著對Web.config進行一下配置,首先看根目錄下的Web.config:

<?xml version="1.0"?>
<configuration>
    <system.web>
        <authentication mode="Forms">
            <forms timeout="600" slidingExpiration="true" loginUrl="~/SignIn.aspx" />
        </authentication>
    </system.web>
   
    <location path="AuthOnly.aspx" >
        <system.web>
            <authorization>
                <deny users="?" />
            </authorization>
        </system.web>
    </location>
</configuration>

這 里我們指定了采用Forms驗證,并且設置用戶身份驗證過期時間為600分鐘(默認為30分鐘),slidingExpiration的意思是說 timeout采用絕對時間還是滑動時間,當采用滑動時間時,如果在timeout時間內再次瀏覽頁面,用戶的最后活躍時間將設為當前時間,并重新開始計 算,這里我們采用滑動時間。loginUrl指定了登錄頁面,當匿名用戶訪問需要驗證后才能訪問的頁面時,將會到自動導航到這里所設置的 SignIn.aspx頁面,默認為Login.aspx。

接著我們指定AuthOnly.aspx頁面為只有驗證過的用戶才可以訪問。然后創建了AuthOnly文件夾,在其下添加了一個web.config,對這個目錄進行設置,指定該文件夾下所有文件只允許驗證用戶進行訪問。

<configuration>
    <system.web>
         <authorization>
             <deny users="?" />
         </authorization>
    </system.web>
</configuration>

創建用戶數據表和數據訪問

既然是用戶登錄,所以我們自然需要一張用戶表,在App_Data下創建一個SiteData數據庫,然后添加一張User用戶表,表的設置如下:

這 個表模擬了一個小型的論壇用戶表,字段的含義基本都是自解釋的,UserImage是用戶頭像的地址,PostCount是用戶的發帖 數,ReplyCount是用戶的回帖數,Level是用戶的級別。我已經為表中添加了兩條范例數據,其中一條用戶名為JimmyZhang,密碼為 password。

接下來我們需要添加一個存儲過程,這個存儲過程接收一個name參數,和一個password輸出參數,根據name判斷User表中是否存在該用戶,如果存在,則由password帶回正確的密碼:

ALTER PROCEDURE dbo.IsValidUser
(
    @userName varchar(50),
    @password varchar(50) OUTPUT
)
AS
    if Exists(Select Id From [User] Where [Name] = @userName)
        Begin
            Select @password = Password From [User] Where [name]= @userName
            Select 1        -- Ture
        End    
Select 0        -- false

這 樣做的目的是為了程序能夠區分“不存在此用戶”和“用戶存在,但是密碼不正確”這兩種情況。如果Select的where子句為 [name]=@userName and [password] = @password,則無法進行區分。由數據庫帶回了正確的密碼之后,我們只需要在程序中與用戶輸入的密碼進行對比就可以知道用戶的密碼是否正確。

接 下來我們創建一個強類型DataSet作為我們的數據訪問層,因為我發現使用強類型DataSet作數據訪問是最快的,基本不需要編寫一行代碼,在 App_Code中添加一個AuthDataSet數據集文件,然后將User表拖進去,另外配置一下UserTableAdapter,添加兩個方法, 一個是GetUserTable(@name),它根據name參數獲得用戶信息;一個是IsValidUser(@userName, @password),它調用了上面的存儲過程,并且返回一個標量值(0或者1)。

配置好以后,你的AuthDataSet應該和下面一樣:

如果你查看一下生成的IsValidUser()方法,就會發現它具有這樣的簽名:

public virtual object IsValidUser(string userName, ref string password)

由于它返回的是一個object類型,并且接收的是一個ref參數,盡管這樣最通用,但是可能不夠方便,注意到UserTableAdapter是一個部分類,所以我們可以在App_Code中再創建一個UserTableAdapter部分類,對它進行一個簡單的包裝:

namespace AuthDataSetTableAdapters {

    // 檢查是否是正確的用戶名,如果是正確的用戶名,帶回正確的密碼
    public partial class UserTableAdapter {    
        public bool IsValidUserST(string userName, out string password) {
            password = "";
            return Convert.ToBoolean(this.IsValidUser(userName, ref password));        
        }
    }
}

這里的方法后綴ST,意思是StrongType(強類型)。好了,現在我們的數據訪問就已經OK了,接下來我們看一下第一個頁面:SignIn.aspx用戶登錄頁面。

用戶登錄 -- 為Identity添加用戶數據

Login.aspx頁面實現

在登錄頁面,我們需要針對登錄用戶和非登錄用戶做不同的處理:如果用戶尚未登錄,則顯示登錄用的表單;如果用于已經登錄了,則顯示登錄用戶名并進行提示。完成這件事最好就是使用LoginView控件和LoginName控件了:

<asp:LoginView ID="LoginView1" runat="server">
    <LoggedInTemplate>
        <asp:LoginName ID="LoginName1" runat="server" />
        ,你已經登錄了^_^ <br /><br />
                 
        你可以選擇 <asp:LoginStatus ID="LoginStatus1" runat="server" LogoutPageUrl="~/Logout.aspx" LogoutAction="Redirect"  />
    </LoggedInTemplate>
    <AnonymousTemplate>
        用戶名:<asp:TextBox ID="txtUserName" runat="server" Width="128px"></asp:TextBox>
        <br />
        密 碼:<asp:TextBox ID="txtPassword" runat="server"></asp:TextBox>
        <br />
        <asp:Button ID="btnLogin" runat="server" Text="登 錄" onclick="btnLogin_Click" Width="100px" />
        <br />
        <br />
        <asp:Label ID="lbMessage" runat="server" ForeColor="Red" Text=""></asp:Label>
    </AnonymousTemplate>
</asp:LoginView>

這 里的關鍵是“登錄”按鈕的代碼后置文件,在“引言”部分,我們提到了Identity中的信息太少,為了向Identity中添加信息,我們可以先獲得 FormsIdentity的Ticket屬性,它是一個FormsAuthenticationTicket類型,它含有一個UserData字符串屬 性可以用于承載我們的用戶數據,遺憾的是這個屬性是只讀的,為了給這個屬性賦值,我們需要重新新構建一個 FormsAuthenticationTicket,并在構造函數中傳入我們想要添加的用戶信息。 FormasAuthenticationTicket包含了諸多用于用戶驗證的信息,它從Cookie中獲得,可以認為它是服務端對Cookie的一個 包裝,只是這里的Cookie的操作不需要我們來處理,而由Asp.Net運行時去處理。具體的代碼如下:

public partial class SignIn : System.Web.UI.Page {

    private enum LoginResult {
        Success,
        UserNotExist,
        PasswordWrong
    }

    // 用戶登錄
    private LoginResult Login(string userName, string password) {

        string validPassword;   // 包含正確的密碼
        AuthDataSetTableAdapters.UserTableAdapter adapter =
            new AuthDataSetTableAdapters.UserTableAdapter();

        // 判斷用戶名是否正確
        if (adapter.IsValidUserST(userName, out validPassword)) {
            // 判斷密碼是否正確
            if (password.Equals(validPassword))
                return LoginResult.Success;
            else
                return LoginResult.PasswordWrong;
        }

        // 用戶名不存在
        return LoginResult.UserNotExist;
    }
   
    protected void btnLogin_Click(object sender, EventArgs e) {

        TextBox txtUserName = LoginView1.FindControl("txtUserName") as TextBox;
        TextBox txtPassword = LoginView1.FindControl("txtPassword") as TextBox;
        Label lbMessage = LoginView1.FindControl("lbMessage") as Label;

        string userName = txtUserName.Text;
        string password = txtPassword.Text;

        LoginResult result = Login(userName, password);

        string userData = "登錄時間" + DateTime.Now.ToString();

        if (result == LoginResult.Success) {
            SetUserDataAndRedirect(userName, userData);        
        } else if (result == LoginResult.UserNotExist) {
            lbMessage.Text = "用戶名不存在!";
        }else {
            lbMessage.Text = "密碼有誤!";
        }
    }


    // 添加自定義的值,然后導航到來到此頁面之前的位置
    private void SetUserDataAndRedirect(string userName, string userData) {
        // 獲得Cookie
        HttpCookie authCookie = FormsAuthentication.GetAuthCookie(userName, true);

        // 得到ticket憑據
        FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);

        // 根據之前的ticket憑據創建新ticket憑據,然后加入自定義信息
        FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(
            ticket.Version, ticket.Name, ticket.IssueDate,
            ticket.Expiration, ticket.IsPersistent, userData);

        // 將新的Ticke轉變為Cookie值,然后添加到Cookies集合中
        authCookie.Value = FormsAuthentication.Encrypt(newTicket);
        HttpContext.Current.Response.Cookies.Add(authCookie);

        // 獲得 來到登錄頁之前的頁面,即url中return參數的值
        string url = FormsAuthentication.GetRedirectUrl(userName, true);

        Response.Redirect(url);
    }
}

我 們首先定義了一個枚舉,用來說明點擊登錄后的狀態:Success(成功)、UserNotExsit(用戶不存在)以及PasswordWrong(用 戶名存在,但密碼錯)。Login()方法調用了上一小節我們定義的強類型DataSet中的IsUserValidST()方法,然后返回登錄結果。“ 搜索”按鈕的事件處理方法反而非常簡單,如果登錄失敗時在頁面顯示失敗原因,如果登錄成功則調用SetUserDataAndRedirect()方法。

在 SetUserDataAndRedirect()方法中,我們執行了主要的邏輯,我們先獲得了Asp.Net用于驗證的Cookie,從Cookie中 得到FormsAuthenticationTicket,最后,執行了前面所敘述的步驟,將我們自定義的數據 -- 當前用戶的登錄時間記錄到了一個新構建的FormsAuthenticationTicket中,最后將它進行編碼然后賦值給Cookie。接著我們導航 到了來到SignIn.aspx之前所在的頁面。

Default.aspx 頁面預覽

默認情況下SignIn.aspx在登錄成功后會導航到Default.aspx頁面,所以我們先簡單的構建一下Default.aspx頁面,看看實現的效果:

<asp:LoginView ID="LoginView1" runat="server">
    <AnonymousTemplate>
        歡迎訪問, 游客 !     
    </AnonymousTemplate>
    <LoggedInTemplate>
        你好, <asp:LoginName ID="LoginName1" runat="server" /> ! <br />
        <strong>UserData值:</strong>
        <asp:Literal ID="lbUserData" runat="server" />
    </LoggedInTemplate>
</asp:LoginView>
<br />
<asp:LoginStatus ID="LoginStatus1" runat="server" LogoutPageUrl="~/Logout.aspx" LogoutAction="Redirect"  />

類似地,我們放置了一個LoginView控件,只是這里我們多放置了一個LoginStatus控件。接下來我們看一下后置代碼:

protected void Page_Load(object sender, EventArgs e) {

    if (!IsPostBack) {
        if (Request.IsAuthenticated) {
            FormsIdentity identity = User.Identity as FormsIdentity;
            string userData = identity.Ticket.UserData;
            Literal lbUserData = LoginView1.FindControl("lbUserData") as Literal;
            lbUserData.Text = userData;
        }
    }
}

最后我們先進行登錄,然后再打開Default.aspx頁面,會看到類似這樣的輸出:

至此,我們已經看到了如何利用FormsAuthentionTicket來附帶額外的用戶數據,但是我們應該看到這種做法存在的問題:可以保存的數據過于單一,僅僅只是一個字符串。而我們第一節中所介紹的用戶表包括各種類型的各種數據。如果你看過了 從一個范例看XML的應用 這篇文章,你應該立刻想到此處又是一個“單一字符串保存多種不同類型數據”的應用場景,我們可以定義XML來解決。對于這種方式,我不再演示了。實際上,我們可以自定義一個IPrincipal和IIdentity來完成,接下來就來看一下。

自定義IPrincipal和IIdentity

不 管是在Windows上還是在Web上,.Net都使用這兩個接口來實現用戶的身份驗證。它們不過是一個接口,實現了這兩個接口的類型附帶了用戶的信息, 最終被賦予線程(Windows)或Cookie(Web)來對用戶進行驗證。我們在App_Code下添加CustomPrincipal和 CustomIdentity來實現這兩個接口:

public class CustomPrincipal : IPrincipal {

    private CustomIdentity identity;

    public CustomPrincipal(CustomIdentity identity) {
        this.identity = identity;
    }

    public IIdentity Identity {
        get {
            return identity;
        }
    }

    public bool IsInRole(string role) {
        return false;
    }
}

public class CustomIdentity : IIdentity {
    private FormsAuthenticationTicket ticket;
    private HttpContext context = HttpContext.Current;

    public CustomIdentity(FormsAuthenticationTicket ticket) {
        this.ticket = ticket;
    }

    public string AuthenticationType {
        get { return "Custom"; }
    }

    public bool IsAuthenticated {
        get { return true; }
    }

    public string Name {
        get {
            return ticket.Name;
        }
    }

    public FormsAuthenticationTicket Ticket {
        get { return ticket; }
    }

    // 這里可以是任意來自數據庫的值,由Name屬性取得
    // 需要注意此時已通過身份驗證
    public string Email {
        get {
            HttpCookie cookie = context.Request.Cookies["Email"];

            if (cookie==null || String.IsNullOrEmpty(cookie.Value)) {
                string type = "jimmy_dev[at]163.com";   // 實際應根據name屬性從數據庫中獲得
                cookie = new HttpCookie("UserType", type);
                cookie.Expires = DateTime.Now.AddDays(1);
                context.Response.Cookies.Add(cookie);
            }

            return cookie.Value;
        }
    }

    public string HomePage {
        get {
            HttpCookie cookie = context.Request.Cookies["HomePage"];

            if (cookie==null || String.IsNullOrEmpty(cookie.Value)) {
                string name = "www.tracefact.net";      // 實際應根據name屬性從數據庫中獲得
                cookie = new HttpCookie("NickName", name);
                cookie.Expires = DateTime.Now.AddDays(1);
                context.Response.Cookies.Add(cookie);
            }
            return cookie.Value;
        }
    }
}

注意這里的HomePage和Email這兩個屬性,它們攜帶了我們的用戶數據,這里我僅僅是對它們進行了一個簡單的賦值,實際的數值應該是來自于數據庫。還要注意獲取到它們的值后被保存在了Cookie中,以避免頻繁的對數據庫進行訪問。

定 義了實現這兩個接口的對象之后,我們還需要把它嵌入到應用程序的生命周期中,具體的做法就是掛接到HttpModule或者是重寫Global.asax 中的事件,這里我采用了重寫Global.asax事件的方式,因此創建一個Global.asax文件,然后添加如下代碼:

void Application_OnPostAuthenticateRequest(object sender, EventArgs e) {
    IPrincipal user = HttpContext.Current.User;

    if (user.Identity.IsAuthenticated
        && user.Identity.AuthenticationType == "Forms") {

        FormsIdentity formIdentity = user.Identity as FormsIdentity;
        CustomIdentity identity = new CustomIdentity(formIdentity.Ticket);

        CustomPrincipal principal = new CustomPrincipal(identity);
        HttpContext.Current.User = principal;

        Thread.CurrentPrincipal = principal;
    }
}

這段代碼很好理解,它不過是在應用程序的PostAuthenticateRequest事件中用我們自定義的CustomPrincipal和CustomIdentity替換掉了默認的IPrincipal和IIdentity實現。

Default.aspx頁面預覽

我們再次對Default.aspx進行修改,添加兩個Literal控件,用于顯示我們自定義的數值:

自定義Identity中的值:<br />
<strong>Email:</strong>
<asp:Literal ID="ltrEmail2" runat="server"></asp:Literal><br />

<strong>HomePage:</strong>
<asp:Literal ID="ltrHomePage" runat="server"></asp:Literal><br />

然后修改頁面的代碼,使用我們的自定義CustomIdentity,然后從中獲得自定義的屬性值:

protected void Page_Load(object sender, EventArgs e) {

    if (!IsPostBack) {
        if (Request.IsAuthenticated) {

            CustomIdentity identity = User.Identity as CustomIdentity;
            if (identity != null) {
                // 獲得UserData中的值
                string userData = identity.Ticket.UserData;
                Literal lbUserData = LoginView1.FindControl("lbUserData") as Literal;
                lbUserData.Text = userData;

                // 獲得identity中的值
                ltrEmail2.Text = identity.Email;
                ltrHomePage.Text = identity.HomePage;
            }
        }
    }
}

如果你現在打開頁面,將會看到類似下面的頁面:

可 以看到我們獲得了定義在CustomIdentity中的屬性。注意這里我只是做了一個示范,因此只在CustomIdentity中包含了Email和 HomePage兩個屬性值,如果看到此處你便以為大功告成,然后將所有未完成的屬性都添加到CustomIdentity中去就大錯特錯了。Identity 的目的只是為你提供一個已經登錄了的用戶的名稱,而不是攜帶所有的用戶信息,這些信息應該由其他的類型提供。因此微軟才定義了 MemberShipUser類型和Profile。從這個角度上來看,自定義IPrincipal和IIdentity并沒有太大的意義。

這里,我們最好是定義一個自己的類型來承載用戶數據,下面我們就看下如何完成。

自定義類型攜帶用戶數據

在App_Code中新建一個SiteUser類,它的實現如下,簡單起見,我使用了公有字段而非屬性:

public class SiteUser
{
    public string Name;
    public string UserImage;
    public DateTime RegisterDate;
    public string Email;
    public string HomePage;
    public int PostCount;
    public int ReplyCount;
    public byte Level;

    public SiteUser(AuthDataSet.UserRow userRow) {
        this.Email = userRow.Email;
        this.HomePage = userRow.Homepage;
        this.Level = userRow.Level;
        this.Name = userRow.Name;
        this.PostCount = userRow.PostCount;
        this.RegisterDate = userRow.RegisterDate;
        this.ReplyCount = userRow.ReplyCount;
        this.UserImage = userRow.UserImage;
    }

    // 實際應該由數據庫獲得
    public static SiteUser GetUser(string name) {

        AuthDataSetTableAdapters.UserTableAdapter adapter
            = new AuthDataSetTableAdapters.UserTableAdapter();
        AuthDataSet.UserDataTable userTable = adapter.GetUserTable(name);
       
        if(userTable.Rows.Count >0){
            return new SiteUser((AuthDataSet.UserRow)userTable.Rows[0]);
        }

        // 因為調用這個方法時,name應該是有效的,
        // 如果name無效,直接拋出異常
        throw new ApplicationException("User Not Found");
    }
}

它的GetUser()靜態方法根據用戶的名稱獲得了一個SiteUser對象,這里需要注意的是通常調用這個方法時,用戶已經登錄過了,也就是說其name參數總是有效的,因此當搜索數據庫找不到記錄時,我簡單地拋出了異常。

Default.aspx 頁面預覽

我們再次修改Default.aspx,添加用于顯示用戶詳細信息的控件和HTML標記:

<asp:Panel ID="pnlUserInfo" runat="server" Visible="false">
    <table class="mainTable" style="width:280px">
        <tr>
            <th style="background:#f5f5f5;text-align:center" colspan="2">用戶信息</th>
        </tr>
        <tr>
            <td colspan="2" style="text-align: center">
                <asp:Image ID="imgHeadImage" runat="server" />
            </td>
        </tr>
        <tr>
            <td style="width:28%;">姓名:</td>
            <td>
                <asp:Literal ID="ltrName" runat="server"></asp:Literal>
            </td>
        </tr>
        <tr>
            <td>注冊日期:</td>
            <td>
                <asp:Literal ID="ltrRegisterDate" runat="server"></asp:Literal>
            </td>
        </tr>
        <tr>
            <td>電子郵件:</td>
            <td>
                <asp:Literal ID="ltrEmail" runat="server"></asp:Literal>
            </td>
        </tr>
        <tr>
            <td>個人主頁:</td>
            <td>
                <asp:HyperLink ID="lnkHomepage" runat="server"></asp:HyperLink>
            </td>
        </tr>
        <tr>
            <td>發帖數:</td>
            <td>
                <asp:Literal ID="ltrPostCount" runat="server"></asp:Literal>
            </td>
        </tr>
        <tr>
            <td>回帖數:</td>
            <td>
                <asp:Literal ID="ltrReplyCount" runat="server"></asp:Literal>
            </td>
        </tr>
        <tr>
            <td>用戶等別:</td>
            <td>
                <asp:Image ID="imgUserLevel" runat="server" />
            </td>
        </tr>
    </table>
</asp:Panel>

然后修改頁面的后置代碼:

protected void Page_Load(object sender, EventArgs e) {
    if (!IsPostBack) {
        if (Request.IsAuthenticated) {

            // 上面相同

            SiteUser user = SiteUser.GetUser(identity.Name);
            PopulateControls(user);
        }
    }
}

private void PopulateControls(SiteUser user) {
    ltrEmail.Text = user.Email;
    ltrName.Text = user.Name;
    ltrPostCount.Text = user.PostCount.ToString();
    ltrRegisterDate.Text = user.RegisterDate.ToShortDateString();
    ltrReplyCount.Text = user.ReplyCount.ToString();
    lnkHomepage.Text = user.HomePage;
    lnkHomepage.NavigateUrl = user.HomePage;

    imgHeadImage.ImageUrl = "~/Images/" + user.UserImage;
    imgUserLevel.ImageUrl =
        String.Format("~/Images/Star{0}.gif", user.Level);

    pnlUserInfo.Visible = true;
}

這里,我們從Identity中獲得用戶的名稱,然后再調用SiteUser的GetUser()方法,獲得了一個SiteUser對象,最后使用這個SiteUser對象對頁面控件進行了賦值。下面是最后的頁面效果:

總結

在這篇文章中我們看到了如何使用Asp.Net內置機制實現用戶驗證的功能,并且通過FormsAuthenticationTicket的UserData屬性、自定義IPrincipal和IIdentity、以及自定義對象三種方式實現了附加 (獲取)用戶數據。

本 文所附帶的代碼中,還有一些頁面只有登錄用戶才能進行訪問,但在這篇文章中我沒有演示和說明。使用.Net的驗證機制,我們可以通過僅在 Web.config設置一下,就擁有了以前需要編碼才能實現的限制頁面訪問的功能。通過這篇文章,我希望大家能夠看到,大多數情況下,我們都可以使 用.Net的自定義驗證系統,而沒有必要再重復造輪子去實現自己的驗證方式。

感謝閱讀,希望這篇文章能給你帶來幫助。

0
0
 
標簽:Asp.Net
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()