閱讀426 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[.NET 基於角色安全性驗證] 之四:ASP.NET 2.0 成員資格和角色管理授權

從嚴格意義上來說,ASP.NET 2.0 的成員資格、角色管理授權和 .NET 角色安全性沒有多大關係。隻不過,Microsoft 替我們完成了一些原本需要我們自己進行的工作而已。

在這兩種新的技術中使用的"提供程序模型"倒是值得我們好好學習一下,因為這個 IoC 概念非常相似。

成員資格

成員資格提供了通用的用戶管理功能,諸如注冊、登錄、找回密碼等,加上與之配套的可視化控件,我們“幾乎”不用在編寫額外的代碼就可以工作。實際上真是如此嗎?MemebershipUser 屬性太少,顯然不大適合我們的商業應用;注冊和登錄控件有缺乏驗證碼,缺乏安全性;…… 當然我們還可以使用自定義驗證和提供程序,不過如此一來還不如自己參考"提供者模型"開發一套呢,畢竟獨立的 Passport 才是我們所需要的,更不要說和既有係統進行整合了,總之 ASP.NET 提供的這個新功能,食之無味,棄之可惜。有關成員資格和角色管理的使用不是本文的重點,詳情可參考 MSDN 文檔。

成員資格由 Membership、MembershipUser、MembershipProvider 組成,Membership 是個靜態類,為用戶提供了大量用戶相關的操作方法,MembershipUser 則是用戶實體類,除了用戶屬性外也有一些用戶自身的操作方法,而 MembershipProvider 自然就是提供程序的基礎抽象類了,它規範了提供程序的接口。

uploads/200608/10_152024_membership.png


我們分析一下 Membership 代碼(局部代碼),看看如何獲取真實的目標提供程序對象的。其實過程也很簡單,讀取配置獲取類型信息,然後用反射創建目標提供程序對象實例。

System.System.Web.Security.Membership
// 我們從 ValidateUser 方法入手。
public static bool ValidateUser(string username, string password)
{
  // 通過 Provider 屬性獲取提供程序對象
  return Membership.Provider.ValidateUser(username, password);
}

public static MembershipProvider Provider
{
  get
  {
    // 應該是通過 Initialize() 方法獲取,並賦值給 s_Provider 變量的。
    Membership.Initialize();
    return Membership.s_Provider;
  }
}

private static void Initialize()
{
  // 從配置文件中讀取配置信息
  RuntimeConfig config1 = RuntimeConfig.GetAppConfig();
  MembershipSection section1 = config1.Membership;

  Membership.s_Providers = new MembershipProviderCollection();

  // 使用 ProvidersHelper.InstantiateProviders 方法從配置信息中讀取設置。
  ProvidersHelper.InstantiateProviders(section1.Providers, Membership.s_Providers, typeof(MembershipProvider));

  // 將缺省提供程序對象賦值給 s_Provider 變量。
  Membership.s_Provider = Membership.s_Providers[section1.DefaultProvider];
  Membership.s_Initialized = true;
}

public static void ProvidersHelper.InstantiateProviders(ProviderSettingsCollection configProviders, ProviderCollection providers, Type providerType)
{
  // 循環讀取配置信息,並創建提供程序對象。
  foreach (ProviderSettings settings1 in configProviders)
  {
    providers.Add(ProvidersHelper.InstantiateProvider(settings1, providerType));
  }
}

public static ProviderBase ProvidersHelper.InstantiateProvider(ProviderSettings providerSettings, Type providerType)
{
  ProviderBase base1 = null;
  try
  {
    // 調用 HttpRuntime.CreatePublicInstance 創建提供程序對象。
    base1 = (ProviderBase)HttpRuntime.CreatePublicInstance(type1);
  }
  catch (Exception exception1)
  {
  }
  return base1;
}

internal static object HttpRuntime.CreatePublicInstance(Type type)
{
  // 利用反射創建對象
  return Activator.CreateInstance(type);
}

下麵我們分析一下 Login 控件的源碼,我們會發現其內部不過是自動調用 Membership 和 MembershipProvider 進行操作而已。

System.Web.UI.WebControls.Login
private void AttemptLogin()
{
  if ((this.Page == null) || this.Page.IsValid)
  {
    LoginCancelEventArgs args1 = new LoginCancelEventArgs();
    this.OnLoggingIn(args1);
    if (!args1.Cancel)
    {
      AuthenticateEventArgs args2 = new AuthenticateEventArgs();

      // 調用核心代碼
      this.OnAuthenticate(args2);

      // 看到了什麼?標準的身份驗證代碼。詳情可以查看上一篇Blog。
      if (args2.Authenticated)
      {
        FormsAuthentication.SetAuthCookie(this.UserNameInternal, this.RememberMeSet);
        this.OnLoggedIn(EventArgs.Empty);
        this.Page.Response.Redirect(this.GetRedirectUrl(), false);
      }
      else
      {
        this.OnLoginError(EventArgs.Empty);
        if (this.FailureAction == LoginFailureAction.RedirectToLoginPage)
        {
          FormsAuthentication.RedirectToLoginPage("loginfailure=1");
        }
        ITextControl control1 = (ITextControl)this.TemplateContainer.FailureTextLabel;
        if (control1 != null)
        {
          control1.Text = this.FailureText;
        }
      }
    }
  }
}

protected virtual void OnAuthenticate(AuthenticateEventArgs e)
{
  AuthenticateEventHandler handler1 = (AuthenticateEventHandler)base.Events[Login.EventAuthenticate];
  if (handler1 != null)
  {
    // 用戶使用了自定義身份驗證服務
    handler1(this, e);
  }
  else
  {
    // 使用成員資格提供程序
    this.AuthenticateUsingMembershipProvider(e);
  }
}

private void AuthenticateUsingMembershipProvider(AuthenticateEventArgs e)
{
  // 使用 LoginUtil.GetProvider 獲取成員資格提供程序對象,並調用其 ValidateUser 進行身份驗證。
  e.Authenticated = LoginUtil.GetProvider(this.MembershipProvider).ValidateUser(this.UserNameInternal, this.PasswordInternal);
}

internal static LoginUtil.MembershipProvider GetProvider(string providerName)
{
  if (string.IsNullOrEmpty(providerName))
  {
    return Membership.Provider;
  }

  // 調用 Membership 相關屬性,獲取提供程序對象。
  MembershipProvider provider1 = Membership.Providers[providerName];
  if (provider1 == null)
  {
    throw new HttpException(SR.GetString("WebControl_CantFindProvider"));
  }
  return provider1;
}

角色管理授權

在實際開發中,除了用戶(User)和角色(Role)以外,我們還需要權限(Permission)這個概念。角色更像是用戶組(Users Group),用戶可以加入一個或多個用戶組,每個用戶組又擁有多個權限。有了權限,我們就可以動態賦予用戶組不同的權力,比如我們可以臨時授予 A 組執行 XX 的權力,三天後我們再取消該權力,顯然沒有權限的設計會缺乏靈活性。

ASP.NET 2.0 的角色管理授權,由 RoleManagerModule、Roles、RoleProvider、RolePrincipa 共同組成。係統會自動載入 RoleManagerModule 這個 HttpModule,Roles 為用戶提供角色相關的操作方法,而 RoleProvider 自然是提供程序的抽象類了。至於 RolePrincipa 幹什麼用,看看下麵的代碼自然就明白了。

System.Web.Security.RoleManagerModule
private void OnEnter(object source, EventArgs eventArgs)
{
  // ...
  // 注意下麵的代碼, RoleManagerModule 使用 RolePrincipal 替代了缺省的 GenericPrincipal。
  if (!(context1.User is RolePrincipal))
  {
    context1.User = new RolePrincipal(context1.User.Identity);
  }
}
 

最後更新:2017-04-02 00:06:27

  上一篇:go “多態”一個需要注意的問題
  下一篇:go 關於測試代碼