65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Web 服务的自定义成员资格提供程序和角色提供程序实现

2006年2月11日

CPOL

7分钟阅读

viewsIcon

1072960

downloadIcon

8371

自定义 MembershipProvider 和 RoleProvider 实现,它们使用 Web 服务来分离应用程序和数据库服务器。

最新

如果您正在使用 Visual Studio 2008,那么使用 .NET 3.5 中提供的 WCF 身份验证服务可能会更好。

WCF 身份验证服务 http://msdn.microsoft.com/en-us/library/bb398990.aspx[^]

WCF 角色 http://msdn.microsoft.com/en-us/library/bb398911.aspx[^]

WCF Profile http://msdn.microsoft.com/en-us/library/bb514968.aspx[^]

我想感谢 Chris Mankowski 促使[^] 我去做我一直想做的事情。

引言

在 ASP.NET 2.0 的最新版本中,我们引入了一套新的控件,使开发人员能够非常轻松地为应用程序添加相当丰富的安全模型和 UI。所有这些控件都使用通过 `web.config` 文件中的配置加载的提供程序。

默认情况下,开发人员被引导使用本地 SQLEXPRESS 数据库,但是她/他可以通过连接字符串轻松地将默认的基于 SQL 的提供程序重定向到使用不同的数据库。并且可以使用 `aspnet_regsql` 工具创建一个新的(或更新现有的)数据库,其中包含所需的数据库架构。

其他第三方提供程序已被创建,以便开发人员可以使用 MySql[^]、ODBC[^] 甚至经典的 web.config[^] 来存储角色/成员身份数据,并且有大量关于编写与其他数据库或替代数据库架构一起工作的提供程序的资源。然而,据我所知,目前发布的所有提供程序都需要被保护的应用程序能够直接访问数据库,这在实施了 N 层结构并且应用程序只能通过 Web 服务访问数据库的商业环境中并不总是理想的,甚至不可能。

我在这里打算演示的是如何编写自己的提供程序集,这些提供程序将与提供的 ASP.NET 控件兼容,并且能够使用由 Web 服务处理的提供程序。

要求

既然我们要实现一个 Web 服务,那么最好能够让多个应用程序使用该 Web 服务,但使用它与不同的数据库或不同的提供程序集进行通信。此外,一些提供程序的实现,例如默认的 SQL 提供程序 SqlRoleProvider[^] 和 SqlMembershipProvider[^],允许您在一个数据库中存储多个应用程序的角色和用户。

考虑到这些需求,我们需要确保我们的提供程序设计允许开发人员在请求到达 Web 服务时控制使用哪个提供程序,以及 Web 服务的 URL。选择应用程序名称已经是基类提供程序的一个属性。我们也很懒惰,希望编写尽可能少的代码,并利用框架来完成繁重的工作,这样我们就可以专注于更苛刻的、不断变化的客户需求。

实现 RoleProvider Web 服务

RoleProvider 是我们需要实现的两个提供程序中最简单的。下面是基类 RoleProvider 支持但我们需要在 Web 服务中支持的方法列表

public abstract class RoleProvider : ProviderBase
{
      // Methods
      protected RoleProvider();
      public abstract void AddUsersToRoles(string[] usernames,
                                           string[] roleNames);
      public abstract void CreateRole(string roleName);
      public abstract bool DeleteRole(string roleName,
                                    bool throwOnPopulatedRole);
      public abstract string[] FindUsersInRole(string roleName,
                                        string usernameToMatch);
      public abstract string[] GetAllRoles();
      public abstract string[] GetRolesForUser(string username);
      public abstract string[] GetUsersInRole(string roleName);
      public abstract bool IsUserInRole(string username,
                                               string roleName);
      public abstract void RemoveUsersFromRoles(string[] usernames,
                                                string[] roleNames);
      public abstract bool RoleExists(string roleName);

      // Properties
      public abstract string ApplicationName { get; set; }
}

属性是我们唯一的有状态部分,因此我们可以将其作为参数传递给每个调用。由于我们希望能够使用任何提供程序来实现此服务,因此我们需要某种配置来允许开发人员将新的提供程序添加到混合中。幸运的是,框架已经为我们提供了这一点,并且可以在 Web 服务应用程序的 `web.config` 中添加条目。

<roleManager enabled="true">
    <providers>
        <clear />
        <add applicationName="/"
            connectionStringName="TrustSecurity"
            name="AspNetSqlRoleProvider"
            type="System.Web.Security.SqlRoleProvider" />
    </providers>
</roleManager>

为了使用此配置,我们可以使用 `System.Web.Security.Roles.Providers` 集合,其中提供程序名称由 `web.config` 中的 `name` 属性设置。鉴于我们拥有想要使用的提供程序名称以及想要与该提供程序一起使用的应用程序名称,我们可以使用以下方法来选择一个提供程序并设置该提供程序的应用程序名称。

protected System.Web.Security.RoleProvider GetProvider(
                       string providerName, string applicationName)
{
    System.Web.Security.RoleProvider provider;
    if ((providerName != null) &&
        (System.Web.Security.Roles.Providers[providerName] != null))
    {
      provider = System.Web.Security.Roles.Providers[providerName];
    }
    else
    {
      provider = System.Web.Security.Roles.Provider;
    }

    if (applicationName != null)
    {
      provider.ApplicationName = applicationName;
    }

    return provider;
}

使用上述方法现在可以非常简单地定义我们的 Web 服务接口。

  [WebMethod(Description="")]
  public void AddUsersToRoles(string providerName,
      string applicationName, string[] usernames, string[] roleNames)
  {
    GetProvider(providerName, applicationName).AddUsersToRoles(
                                            usernames, roleNames);
  }

  [WebMethod(Description = "")]
  public void CreateRole(string providerName, string applicationName,
                                                      string roleName)
  {
    GetProvider(providerName, applicationName).CreateRole(roleName);
  }

  [WebMethod(Description = "")]
  public bool DeleteRole(string providerName, string applicationName,
                            string roleName, bool throwOnPopulatedRole)
  {
    return GetProvider(providerName, applicationName).DeleteRole(
                                       roleName, throwOnPopulatedRole);
  }

  [WebMethod(Description = "")]
  public string[] FindUsersInRole(string providerName,
       string applicationName, string roleName, string usernameToMatch)
  {
    return GetProvider(providerName, applicationName).FindUsersInRole(
                                              roleName, usernameToMatch);
  }

  [WebMethod(Description = "")]
  public string[] GetAllRoles(string providerName, string applicationName)
  {
    return GetProvider(providerName, applicationName).GetAllRoles();
  }

  [WebMethod(Description = "")]
  public string[] GetRolesForUser(string providerName,
                        string applicationName, string username)
  {
    return GetProvider(providerName, applicationName).GetRolesForUser(
                                                              username);
  }

  [WebMethod(Description = "")]
  public string[] GetUsersInRole(string providerName,
                                string applicationName, string roleName)
  {
    return GetProvider(providerName, applicationName).GetUsersInRole(
                                                             roleName);
  }

  [WebMethod(Description = "")]
  public bool IsUserInRole(string providerName, string applicationName,
                                        string username, string roleName)
  {
    return GetProvider(providerName, applicationName).IsUserInRole(
                                                  username, roleName);
  }

  [WebMethod(Description = "")]
  public void RemoveUsersFromRoles(string providerName,
      string applicationName, string[] usernames, string[] roleNames)
  {
    GetProvider(providerName, applicationName).RemoveUsersFromRoles(
                                                usernames, roleNames);
  }

  [WebMethod(Description = "")]
  public bool RoleExists(string providerName, string applicationName,
    string roleName)
  {
    return GetProvider(providerName, applicationName).RoleExists(
                                                           roleName);
  }

实现自定义 RoleProvider 类

自定义 RoleProvider 实现要求我们实现 `System.Web.Security.RoleProvider abstract` 类提供的必需方法。我们还需要将自定义配置(如下面突出显示)添加到 `web.config` 中,并通过 `Initialize` 方法进行提取。

<roleManager defaultProvider="WebServiceRoleProvider" enabled="true">
    <providers>
        <clear />
        <add applicationName="/"
            name="WebServiceRoleProvider"
            type="ManyMonkeys.SecurityProviders.WebServiceRoleProvider"
            roleProviderUri="https:///WTS/RoleProvider.asmx"
            remoteProviderName="AspNetSqlRoleProvider" />
    </providers>
</roleManager>
    public override void Initialize(string name,
       System.Collections.Specialized.NameValueCollection config)
    {
      if (config["roleProviderUri"] != null)
      {
        service.Url = config["roleProviderUri"];
      }

      _ApplicationName = config["applicationName"];
      if (string.IsNullOrEmpty(_ApplicationName))
      {
        _ApplicationName = ProviderUtility.GetDefaultAppName();
      }

      _RemoteProviderName = config["remoteProviderName"];

      base.Initialize(name, config);
    }

现在已经提取了自定义配置,并且 Web 服务消费者已实例化并初始化,其余必需方法的实现如下所示。

public override void AddUsersToRoles(string[] usernames,
                                         string[] roleNames)
{
  service.AddUsersToRoles(_RemoteProviderName,
              _ApplicationName, usernames, roleNames);
}

public override void CreateRole(string roleName)
{
  service.CreateRole(_RemoteProviderName,
                     _ApplicationName, roleName);
}

public override bool DeleteRole(string roleName,
                         bool throwOnPopulatedRole)
{
  return service.DeleteRole(_RemoteProviderName,
     _ApplicationName, roleName, throwOnPopulatedRole);
}

public override string[] FindUsersInRole(string roleName,
                                    string usernameToMatch)
{
  return service.FindUsersInRole(_RemoteProviderName,
                 _ApplicationName, roleName, usernameToMatch);
}

public override string[] GetAllRoles()
{
  return service.GetAllRoles(_RemoteProviderName,
                                    _ApplicationName);
}

public override string[] GetRolesForUser(string username)
{
  return service.GetRolesForUser(_RemoteProviderName,
                              _ApplicationName, username);
}

public override string[] GetUsersInRole(string roleName)
{
  return service.GetUsersInRole(_RemoteProviderName,
                              _ApplicationName, roleName);
}

public override bool IsUserInRole(string username,
                                       string roleName)
{
  return service.IsUserInRole(_RemoteProviderName,
                    _ApplicationName, username, roleName);
}

public override void RemoveUsersFromRoles(string[] usernames,
  string[] roleNames)
{
  service.RemoveUsersFromRoles(_RemoteProviderName,
                     _ApplicationName, usernames, roleNames);
}

public override bool RoleExists(string roleName)
{
  return service.RoleExists(_RemoteProviderName,
                        _ApplicationName, roleName);
}

实现 MembershipProvider Web 服务

用于 RoleProvider 实现的所有技术都可以用于帮助实现 `MembershipProvider`。然而,通过查看 `MembershipProvider` 的要求,很明显这会更复杂一些。

public abstract class MembershipProvider : ProviderBase
{
  // Methods
  public abstract bool ChangePassword(string username,
                   string oldPassword, string newPassword);
  public abstract bool ChangePasswordQuestionAndAnswer(
                         string username, string password,
                         string newPasswordQuestion,
                         string newPasswordAnswer);
  public abstract MembershipUser CreateUser(string username,
      string password, string email, string passwordQuestion,
      string passwordAnswer, bool isApproved,
      object providerUserKey, out MembershipCreateStatus status);
  public abstract bool DeleteUser(string username,
                                      bool deleteAllRelatedData);
  public abstract MembershipUserCollection FindUsersByEmail(
              string emailToMatch, int pageIndex, int pageSize,
              out int totalRecords);
  public abstract MembershipUserCollection FindUsersByName(
         string usernameToMatch, int pageIndex, int pageSize,
         out int totalRecords);
  public abstract MembershipUserCollection GetAllUsers(
         int pageIndex, int pageSize, out int totalRecords);
  public abstract int GetNumberOfUsersOnline();
  public abstract string GetPassword(string username,
                                             string answer);
  public abstract MembershipUser GetUser(
      object providerUserKey, bool userIsOnline);
  public abstract MembershipUser GetUser(string username,
                                         bool userIsOnline);
  public abstract string GetUserNameByEmail(string email);
  public abstract string ResetPassword(string username,
                                           string answer);
  public abstract bool UnlockUser(string userName);
  public abstract void UpdateUser(MembershipUser user);
  public abstract bool ValidateUser(string username,
                                      string password);

  // Properties
  public abstract string ApplicationName { get; set; }
  public abstract bool EnablePasswordReset { get; }
  public abstract bool EnablePasswordRetrieval { get; }
  public abstract int MaxInvalidPasswordAttempts { get; }
  public abstract int MinRequiredNonAlphanumericCharacters
       { get; }
  public abstract int MinRequiredPasswordLength { get; }
  public abstract int PasswordAttemptWindow { get; }
  public abstract MembershipPasswordFormat PasswordFormat
       { get; }
  public abstract string PasswordStrengthRegularExpression
       { get; }
  public abstract bool RequiresQuestionAndAnswer { get; }
  public abstract bool RequiresUniqueEmail { get; }
}

首先,只读属性看起来可能很麻烦,如果我们必须在每次方法调用中将它们传递给 Web 服务或每次请求时从 Web 服务获取它们。然而,这些属性似乎仅适用于 UI,因此在这个练习中,没有真正的必要在 Web 服务之间传递这些值。其次,有一个名为 `MembershipUser` 的对象以及这些对象的集合,我们将找到一种方法来来回回地传递它们。不幸的是,当我们查看 `MembershipUser` 对象时,我们会发现这并不像在 `WebMethod` 中暴露该对象那么简单,因为有许多只读属性需要在 Web 服务之间进行传输,并且只能通过构造函数设置。一个包含所有必需属性的简单类可以用于此目的。

public class MembershipUser
{
  public MembershipUser()
  {
  }

  private string comment;

  public string Comment
  {
    get { return comment; }
    set { comment = value; }
  }

  private DateTime creationDate;

  public DateTime CreationDate
  {
    get { return creationDate; }
    set { creationDate = value; }
  }

  private string email;

  public string Email
  {
    get { return email; }
    set { email = value; }
  }

  private bool isApproved;

  public bool IsApproved
  {
    get { return isApproved; }
    set { isApproved = value; }
  }

  private bool isLockedOut;

  public bool IsLockedOut
  {
    get { return isLockedOut; }
    set { isLockedOut = value; }
  }

  private bool isOnline;

  public bool IsOnline
  {
    get { return isOnline; }
    set { isOnline = value; }
  }

  private DateTime lastActivityDate;

  public DateTime LastActivityDate
  {
    get { return lastActivityDate; }
    set { lastActivityDate = value; }
  }

  private DateTime lastLockoutDate;

  public DateTime LastLockoutDate
  {
    get { return lastLockoutDate; }
    set { lastLockoutDate = value; }
  }

  private DateTime lastLoginDate;

  public DateTime LastLoginDate
  {
    get { return lastLoginDate; }
    set { lastLoginDate = value; }
  }

  private DateTime lastPasswordChangedDate;

  public DateTime LastPasswordChangedDate
  {
    get { return lastPasswordChangedDate; }
    set { lastPasswordChangedDate = value; }
  }

  private string passwordQuestion;

  public string PasswordQuestion
  {
    get { return passwordQuestion; }
    set { passwordQuestion = value; }
  }

  private string providerName;

  public string ProviderName
  {
    get { return providerName; }
    set { providerName = value; }
  }

  private object providerUserKey;

  public object ProviderUserKey
  {
    get { return providerUserKey; }
    set { providerUserKey = value; }
  }

  private string userName;

  public string UserName
  {
    get { return userName; }
    set { userName = value; }
  }
}

现在我们需要方法来允许我们在不同的 `MembershipUser` 对象之间进行转换,并创建可以传输的对象数组。我们需要注意的是,当我们创建一个 `System.Web.Security.MembershipUser` 对象时,我们需要提供对象在其内部方法中需要的提供程序的名称。

  protected System.Web.Security.MembershipProvider GetProvider(
                      string providerName, string applicationName)
  {
    System.Web.Security.MembershipProvider provider;
    if ((providerName != null) &&
      (System.Web.Security.Membership.Providers[providerName] != null))
    {
      provider = System.Web.Security.Membership.Providers[providerName];
    }
    else
    {
      provider = System.Web.Security.Membership.Provider;
    }

    if (applicationName != null)
    {
      provider.ApplicationName = applicationName;
    }

    return provider;
  }

  protected MembershipUser ConvertUser(
         System.Web.Security.MembershipUser user)
  {
    if (user == null) return null;
    MembershipUser membershipUser = new MembershipUser();
    membershipUser.Comment = user.Comment;
    membershipUser.CreationDate = user.CreationDate;
    membershipUser.Email = user.Email;
    membershipUser.IsApproved = user.IsApproved;
    membershipUser.IsLockedOut = user.IsLockedOut;
    membershipUser.IsOnline = user.IsOnline;
    membershipUser.LastActivityDate = user.LastActivityDate;
    membershipUser.LastLockoutDate = user.LastLockoutDate;
    membershipUser.LastLoginDate = user.LastLoginDate;
    membershipUser.LastPasswordChangedDate =
                         user.LastPasswordChangedDate;
    membershipUser.PasswordQuestion = user.PasswordQuestion;
    membershipUser.ProviderName = user.ProviderName;
    membershipUser.ProviderUserKey = user.ProviderUserKey;
    membershipUser.UserName = user.UserName;
    return membershipUser;
  }

  protected System.Web.Security.MembershipUser
    ConvertUser(System.Web.Security.MembershipProvider provider,
        MembershipUser user)
  {
    if (user == null) return null;
    System.Web.Security.MembershipUser membershipUser =
      new System.Web.Security.MembershipUser(provider.Name,
                                user.UserName,
                                user.ProviderUserKey,
                                user.Email,
                                user.PasswordQuestion,
                                user.Comment,
                                user.IsApproved,
                                user.IsLockedOut,
                                user.CreationDate,
                                user.LastLoginDate,
                                user.LastActivityDate,
                                user.LastPasswordChangedDate,
                                user.LastLockoutDate);
    return membershipUser;
  }

  protected List<MembershipUser>
    BuildUserList(System.Web.Security.MembershipUserCollection collection)
  {
    if (collection == null) return null;
    List<MembershipUser> list = new List<MembershipUser>();
    foreach (System.Web.Security.MembershipUser user in collection)
    {
      list.Add(ConvertUser(user));
    }
    return list;
  }

有了这些辅助类,我们就可以创建我们的 Web 服务接口了。

  [WebMethod(Description = "")]
  public bool ChangePassword(string providerName,
           string applicationName, string username,
           string oldPassword, string newPassword)
  {
    return GetProvider(providerName, applicationName).ChangePassword(
                                   username, oldPassword, newPassword);
  }

  [WebMethod(Description = "")]
  public bool ChangePasswordQuestionAndAnswer(string providerName,
           string applicationName, string username, string password,
           string newPasswordQuestion, string newPasswordAnswer)
  {
    return GetProvider(providerName,
      applicationName).ChangePasswordQuestionAndAnswer(username,
                 password, newPasswordQuestion, newPasswordAnswer);
  }

  [WebMethod(Description = "")]
  public MembershipUser CreateUser(string providerName,
    string applicationName, string username, string password,
    string email, string passwordQuestion, string passwordAnswer,
    bool isApproved, object providerUserKey,
    out System.Web.Security.MembershipCreateStatus status)
  {
    return ConvertUser(GetProvider(providerName, applicationName).CreateUser(
      username, password, email, passwordQuestion, passwordAnswer, isApproved,
      providerUserKey, out status));
  }

  [WebMethod(Description = "")]
  public bool DeleteUser(string providerName, string applicationName,
                               string username, bool deleteAllRelatedData)
  {
    return GetProvider(providerName, applicationName).DeleteUser(username,
      deleteAllRelatedData);
  }

  [WebMethod(Description = "")]
  public List<MembershipUser> FindUsersByEmail(string providerName,
                  string applicationName, string emailToMatch,
                  int pageIndex, int pageSize, out int totalRecords)
  {
    return BuildUserList(GetProvider(providerName,
      applicationName).FindUsersByEmail(emailToMatch, pageIndex,
      pageSize, out totalRecords));
  }

  [WebMethod(Description = "")]
  public List<MembershipUser> FindUsersByName(string providerName,
                   string applicationName, string usernameToMatch,
                   int pageIndex, int pageSize, out int totalRecords)
  {
    return BuildUserList(GetProvider(providerName,
      applicationName).FindUsersByName(usernameToMatch, pageIndex,
      pageSize, out totalRecords));
  }

  [WebMethod(Description = "")]
  public List<MembershipUser> GetAllUsers(string providerName,
                             string applicationName, int pageIndex,
                             int pageSize, out int totalRecords)
  {
    return BuildUserList(GetProvider(providerName,
      applicationName).GetAllUsers(pageIndex, pageSize,
                                        out totalRecords));
  }

  [WebMethod(Description = "")]
  public int GetNumberOfUsersOnline(string providerName,
                                    string applicationName)
  {
    return
     GetProvider(providerName, applicationName).GetNumberOfUsersOnline();
  }

  [WebMethod(Description = "")]
  public string GetPassword(string providerName,
          string applicationName, string username, string answer)
  {
    return GetProvider(providerName, applicationName).GetPassword(
                                                   username, answer);
  }

  [WebMethod(Description = "")]
  public MembershipUser GetUserByUserName(string providerName,
      string applicationName, string username, bool userIsOnline)
  {
    return ConvertUser(GetProvider(providerName,
      applicationName).GetUser(username, userIsOnline));
  }

  [WebMethod(Description = "")]
  public MembershipUser GetUserByKey(string providerName,
            string applicationName, object providerUserKey,
            bool userIsOnline)
  {
    return ConvertUser(GetProvider(providerName,
      applicationName).GetUser(providerUserKey, userIsOnline));
  }

  [WebMethod(Description = "")]
  public string GetUserNameByEmail(string providerName,
                       string applicationName, string email)
  {
    return
     GetProvider(providerName, applicationName).GetUserNameByEmail(email);
  }

  [WebMethod(Description = "")]
  public string ResetPassword(string providerName,
      string applicationName, string username, string answer)
  {
    return GetProvider(providerName,
      applicationName).ResetPassword(username, answer);
  }

  [WebMethod(Description = "")]
  public bool UnlockUser(string providerName,
              string applicationName, string userName)
  {
    return
      GetProvider(providerName, applicationName).UnlockUser(userName);
  }

  [WebMethod(Description = "")]
  public void UpdateUser(string providerName,
      string applicationName, MembershipUser user)
  {
    System.Web.Security.MembershipProvider provider =
              GetProvider(providerName, applicationName);
    provider.UpdateUser(ConvertUser(provider, user));
  }

  [WebMethod(Description = "")]
  public bool ValidateUser(string providerName,
     string applicationName, string username, string password)
  {
    return GetProvider(providerName, applicationName).ValidateUser(
                                                 username, password);
  }

实现自定义 MembershipProvider 类

自定义 MembershipProvider 实现要求我们实现 `System.Web.Security.MembershipProvider` abstract 类提供的必需方法。我们还需要将自定义配置(如下面突出显示)添加到 `web.config` 中,并通过 `Initialize` 方法进行提取。

<membership defaultProvider="WebServiceMembershipProvider"
  userIsOnlineTimeWindow="20">
    <providers>
        <clear />
        <add name="WebServiceMembershipProvider"
            type="ManyMonkeys.SecurityProviders.WebServiceMembershipProvider"
            enablePasswordRetrieval="false"
            enablePasswordReset="true"
            requiresQuestionAndAnswer="true"
            passwordFormat="Hashed"
            applicationName="/"
            roleProviderUri="https:///WTS/MembershipProvider.asmx"
            remoteProviderName="AspNetSqlMembershipProvider"/>
    </providers>
</membership>
public override void Initialize(string name,
      System.Collections.Specialized.NameValueCollection config)
{
  if (config["roleProviderUri"] != null)
  {
    service.Url = config["roleProviderUri"];
  }
  _ApplicationName = config["applicationName"];
  if (string.IsNullOrEmpty(_ApplicationName))
  {
    _ApplicationName = ProviderUtility.GetDefaultAppName();
  }

  _EnablePasswordRetrieval = ProviderUtility.GetBooleanValue(
                        config, "enablePasswordRetrieval", false);
  _EnablePasswordReset = ProviderUtility.GetBooleanValue(config,
                                     "enablePasswordReset", true);
  _RequiresQuestionAndAnswer =
         ProviderUtility.GetBooleanValue(config,
         "requiresQuestionAndAnswer", true);
  _RequiresUniqueEmail = ProviderUtility.GetBooleanValue(config,
                                     "requiresUniqueEmail", true);
  _MaxInvalidPasswordAttempts = ProviderUtility.GetIntValue(config,
                         "maxInvalidPasswordAttempts", 5, false, 0);
  _PasswordAttemptWindow = ProviderUtility.GetIntValue(config,
                             "passwordAttemptWindow", 10, false, 0);
  _MinRequiredPasswordLength = ProviderUtility.GetIntValue(config,
                       "minRequiredPasswordLength", 7, false, 0x80);
  _MinRequiredNonalphanumericCharacters =
             ProviderUtility.GetIntValue(config,
             "minRequiredNonalphanumericCharacters", 1, true, 0x80);
  _PasswordStrengthRegularExpression =
             config["passwordStrengthRegularExpression"];

  if (config["passwordFormat"] != null)
  {
    _PasswordFormat =
      (MembershipPasswordFormat)Enum.Parse(
         typeof(MembershipPasswordFormat), config["passwordFormat"]);
  }
  else
  {
    _PasswordFormat = MembershipPasswordFormat.Hashed;
  }

  _RemoteProviderName = config["remoteProviderName"];

  base.Initialize(name, config);
}

还需要方法将 Web 服务消费者中的 `MembershipUser` 对象转换为由控件识别的真正的 `System.Web.Security.MembershipUser` 对象。

    static private MembershipProvider.MembershipUser
      ConvertUser(System.Web.Security.MembershipUser user)
    {
      if (user == null) return null;
      MembershipProvider.MembershipUser membershipUser =
        new MembershipProvider.MembershipUser();
      membershipUser.Comment = user.Comment;
      membershipUser.CreationDate = user.CreationDate;
      membershipUser.Email = user.Email;
      membershipUser.IsApproved = user.IsApproved;
      membershipUser.IsLockedOut = user.IsLockedOut;
      membershipUser.IsOnline = user.IsOnline;
      membershipUser.LastActivityDate = user.LastActivityDate;
      membershipUser.LastLockoutDate = user.LastLockoutDate;
      membershipUser.LastLoginDate = user.LastLoginDate;
      membershipUser.LastPasswordChangedDate =
                           user.LastPasswordChangedDate;
      membershipUser.PasswordQuestion = user.PasswordQuestion;
      membershipUser.ProviderName = user.ProviderName;
      membershipUser.ProviderUserKey = user.ProviderUserKey;
      membershipUser.UserName = user.UserName;
      return membershipUser;
    }

    private System.Web.Security.MembershipUser
      ConvertUser(MembershipProvider.MembershipUser user)
    {
      if (user == null) return null;
      System.Web.Security.MembershipUser membershipUser =
        new System.Web.Security.MembershipUser(this.Name,
                                  user.UserName,
                                  user.ProviderUserKey,
                                  user.Email,
                                  user.PasswordQuestion,
                                  user.Comment,
                                  user.IsApproved,
                                  user.IsLockedOut,
                                  user.CreationDate,
                                  user.LastLoginDate,
                                  user.LastActivityDate,
                                  user.LastPasswordChangedDate,
                                  user.LastLockoutDate);
      return membershipUser;
    }

    private System.Web.Security.MembershipUserCollection
      BuildUserCollection(MembershipProvider.MembershipUser[] list)
    {
      if (list == null) return null;
      System.Web.Security.MembershipUserCollection collection =
        new System.Web.Security.MembershipUserCollection();
      foreach (MembershipProvider.MembershipUser user in list)
      {
        collection.Add(ConvertUser(user));
      }
      return collection;
    }

有了这些辅助方法的帮助,一旦我们实例化了 Web 服务消费者,完成我们的实现就变成了一项简单的任务。

public override bool ChangePassword(string username,
                    string oldPassword, string newPassword)
{
  return service.ChangePassword(_RemoteProviderName,
      _ApplicationName, username, oldPassword, newPassword);
}

public override bool ChangePasswordQuestionAndAnswer(
      string username, string password,
      string newPasswordQuestion, string newPasswordAnswer)
{
  return service.ChangePasswordQuestionAndAnswer(
      _RemoteProviderName, _ApplicationName, username,
      password, newPasswordQuestion, newPasswordAnswer);
}

public override MembershipUser CreateUser(string username,
     string password, string email, string passwordQuestion,
     string passwordAnswer, bool isApproved,
     object providerUserKey, out MembershipCreateStatus status)
{
  MembershipProvider.MembershipCreateStatus newStatus;
  MembershipUser user =
    ConvertUser(service.CreateUser(_RemoteProviderName,
         _ApplicationName, username, password, email,
         passwordQuestion, passwordAnswer, isApproved,
         providerUserKey, out newStatus));
  status =
   (MembershipCreateStatus)Enum.Parse(typeof(MembershipCreateStatus),
                                               newStatus.ToString());
  return user;
}

public override bool DeleteUser(string username,
                                bool deleteAllRelatedData)
{
  return service.DeleteUser(_RemoteProviderName,
             _ApplicationName, username, deleteAllRelatedData);
}

public override MembershipUserCollection FindUsersByEmail(
           string emailToMatch, int pageIndex, int pageSize,
           out int totalRecords)
{
  return BuildUserCollection(service.FindUsersByEmail(
    _RemoteProviderName, _ApplicationName, emailToMatch,
    pageIndex, pageSize, out totalRecords));
}

public override MembershipUserCollection FindUsersByName(
  string usernameToMatch,
  int pageIndex, int pageSize, out int totalRecords)
{
  return BuildUserCollection(service.FindUsersByName(
      _RemoteProviderName, _ApplicationName, usernameToMatch,
      pageIndex, pageSize, out totalRecords));
}

public override MembershipUserCollection GetAllUsers(
        int pageIndex, int pageSize, out int totalRecords)
{
  return BuildUserCollection(service.GetAllUsers(
      _RemoteProviderName, _ApplicationName, pageIndex,
      pageSize, out totalRecords));
}

public override int GetNumberOfUsersOnline()
{
  return service.GetNumberOfUsersOnline(_RemoteProviderName,
                                           _ApplicationName);
}

public override string GetPassword(string username, string answer)
{
  return service.GetPassword(_RemoteProviderName,
                               _ApplicationName, username, answer);
}

public override MembershipUser GetUser(string username,
                                         bool userIsOnline)
{
  return ConvertUser(service.GetUserByUserName(
           _RemoteProviderName, _ApplicationName,
           username, userIsOnline));
}

public override MembershipUser GetUser(object providerUserKey,
                                             bool userIsOnline)
{
  return ConvertUser(service.GetUserByKey(_RemoteProviderName,
             _ApplicationName, providerUserKey, userIsOnline));
}

public override string GetUserNameByEmail(string email)
{
  return service.GetUserNameByEmail(_RemoteProviderName,
                                   _ApplicationName, email);
}

public override string ResetPassword(string username,
                                           string answer)
{
  return service.ResetPassword(_RemoteProviderName,
                         _ApplicationName, username, answer);
}

public override bool UnlockUser(string userName)
{
  return service.UnlockUser(_RemoteProviderName,
                            _ApplicationName, userName);
}

public override void UpdateUser(MembershipUser user)
{
  service.UpdateUser(_RemoteProviderName,
                   _ApplicationName, ConvertUser(user));
}

public override bool ValidateUser(string username,
                                        string password)
{
  return service.ValidateUser(_RemoteProviderName,
                   _ApplicationName, username, password);
}

就这样,我们现在有了一对提供程序,它们可以通过 Web 服务使用任何其他提供程序实现,因此我们的应用程序不需要直接访问数据库服务器。

安全问题

聪明的读者会意识到我们存在一个安全问题,即密码目前正在通过 Web 服务接口以明文形式传输。有两种方法可以立即应对,以缓解此问题。

  1. 使用 SSL 保护应用程序和 Web 服务之间的连接。
  2. 使用 WSE 3.0[^],无论是否使用 SSL,来保护端点并加密数据包中的数据。

提供的代码

提供的代码分为两部分:

  1. 由自定义提供程序调用的 Web 服务
  2. 使用 Web 服务的自定义提供程序

为了使用此代码,您需要托管 Web 服务并修改添加到 `web.config` 的条目,以便可以使用自定义提供程序。

反馈和投票

如果您已经读到这里,请记住投票。如果您喜欢、不喜欢或同意您所读的内容,请在下面的论坛中说明 - 您的反馈很重要!

修订历史

  • 2006年2月11日
    • 原文
  • 2006年2月13日
    • 修正了不明显的拼写错误
  • 2006年2月16日
    • 添加了 VB.NET 示例代码,因为我当时很慷慨 - 不要指望我为我的其他示例也这样做 :)
  • 2009年10月3日
    • 添加了指向 .NET 3.5 中可用的 WCF 身份验证服务的链接
© . All rights reserved.