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


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參數總是有效的,因此當搜索數據庫找不到記錄時,我簡單地拋出了異常。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM