ASP.NETカスタムメンバーシッププロバイダPart.2(実装プロバイダクラス:XmlMembershipProvider)

19748 ワード

この記事では、XmlMembershipProviderクラスを完了します.これは、ストレージとメンバーシップAPIの要件間のアダプタをカスタマイズする役割です.
public class XmlMembershipProvider : System.Web.Security.MembershipProvider { }
各カスタムメンバーシッププロバイダは、このクラスから継承する必要があります.(ASP.NET 4.0でSystem.Web.ApplicationServices.dllに移動)
メンバーシップAPIのニーズを満たすために、多くの属性と方法が必要です.これらのプロパティおよびメソッドは、ユーザーのクエリー、作成、更新、削除、およびパスワード要件などのプロバイダの特定の情報を取得するために使用されます.これらのプロパティは、RequiresQuestionAndAnswerプロパティなどのセキュリティコントロールによってクエリーされます.CreateUserWizardウィザードによってクエリーされます.
これらのプロパティを実装します.これは最も簡単な部分です.各プロパティについて、対応するプロパティステータスを含むプライベート変数を指定します.これらのプロパティの意味は、最下位プロバイダ実装の意味と同じです.プロパティのほとんどはアクセスメソッドのみで、メソッドは設定されていません.では、ASP.NETアーキテクチャはどのようにwebを使います.configで構成された値は、これらのプロパティを初期化しますか?
すべてのプロバイダの元のベースクラスSystem.Configuration.Provider.ProviderBaseで答えが見つかります.Initialize()メソッドを再ロードして、属性のプライベートメンバーを初期化します.
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config){}
 
Xmlの機能を一歩一歩実現します.
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
    if (config == null)
    {
        throw new ArgumentNullException("config");
    }
    if (string.IsNullOrEmpty(name))
    {
        name = "XmlMembershipProvider";
    }
    if (string.IsNullOrEmpty(config["description"]))
    {
        config.Remove("description");
        config.Add("description", "Xml Membership Provider");
    }
    base.Initialize(name, config);
    ......
}

まず、各構成が入力されているかどうかを確認します.プロバイダの構成がない場合は、動作しません.ベースクラスの初期化メソッドを呼び出して、基本的なプロパティを適切に初期化します.次に、プロパティの初期化を開始します.
// Initialize default values
_ApplicationName = "DefaultApp";
_EnablePasswordReset = false;
_PasswordStrengthRegEx = @"[\w| !?%&/()=\-?\*]*";
_MaxInvalidPasswordAttempts = 3;
_MinRequiredNonAlphanumericChars = 1;
_MinRequiredPasswordLength = 5;
_RequiresQuestionAndAnswer = false;
_PasswordFormat = MembershipPasswordFormat.Hashed;
 
// Now go through the properties and initialize custom values
foreach (string key in config.Keys)
{
    switch (key.ToLower())
    {
        case "name":
            _Name = config[key];
            break;
        case "applicationname":
            _ApplicationName = config[key];
            break;
        case "filename":
            _FileName = config[key];
            break;
        case "enablepasswordreset":
            _EnablePasswordReset = bool.Parse(config[key]);
            break;
        case "passwordstrengthregex":
            _PasswordStrengthRegEx = config[key];
            break;
        case "maxinvalidpasswordattempts":
            _MaxInvalidPasswordAttempts = int.Parse(config[key]);
            break;
        case "minrequirednonalphanumericchars":
            _MinRequiredNonAlphanumericChars = int.Parse(config[key]);
            break;
        case "minrequiredpasswordlength":
            _MinRequiredPasswordLength = int.Parse(config[key]);
            break;
        case "passwordformat":
            _PasswordFormat = (MembershipPasswordFormat)Enum.Parse(
                        typeof(MembershipPasswordFormat), config[key]);
            break;
        case "requiresquestionandanswer":
            _RequiresQuestionAndAnswer = bool.Parse(config[key]);
            break;
    }
}

これらのコードは、これらの属性がwebに含まれないようにデフォルト値を初期化する.configプロファイルにあります.ファイル名の設定など、カスタム設定も含めることができます.FileNameプロパティ(メンバーシッププロバイダのデフォルトのプロパティではありません)は、ユーザー情報を格納するXMLファイルを指します.このファイル名をUserStoreクラスに渡します.
private UserStore CurrentStore
{
    get
    {
        if (_CurrentStore == null)
        {
            _CurrentStore = UserStore.GetStore(_FileName);
        }
        return _CurrentStore;
    }
}

このクラスの残りの関数では、このプロパティを使用してデータストレージにアクセスします.
 
プロバイダでは、ユーザーを作成、更新、削除し、ユーザーの詳細にアクセスして取得するための多くの方法が必要です.これらのメソッドは、前に作成したストレージクラスを介してこれらの情報にアクセスします.
 

1.ユーザーを作成し、ストレージに追加


CreateUser()メソッドでは、ユーザー名とEmailアドレスが一意であり、パスワードが有効であり、パスワードの強度の要件を満たしていることを確認する必要があります.
public override MembershipUser CreateUser(string username, string password,
    string email, string passwordQuestion,
    string passwordAnswer, bool isApproved,
    object providerUserKey, out MembershipCreateStatus status)
{
    try
    {
        // Validate the username and email
        if (!ValidateUsername(username, email, Guid.Empty))
        {
            status = MembershipCreateStatus.InvalidUserName;
            return null;
        }
 
        // Raise the event before validating the password
        base.OnValidatingPassword(new ValidatePasswordEventArgs(username, password, true));
 
        // Validate the password
        if (!ValidatePassword(password))
        {
            status = MembershipCreateStatus.InvalidPassword;
            return null;
        }
 
        // Everything is valid, create the user
        SimpleUser user = new SimpleUser();
        user.UserKey = Guid.NewGuid();
        user.UserName = username;
        user.PasswordSalt = string.Empty;
        user.Password = this.TransformPassword(password, ref user.PasswordSalt);
        user.Email = email;
        user.PasswordQuestion = passwordQuestion;
        user.PasswordAnswer = passwordAnswer;
        user.CreationDate = DateTime.Now;
        user.LastActivityDate = DateTime.Now;
        user.LastPasswordChangeDate = DateTime.Now;
 
        // Add the user to the store
        CurrentStore.Users.Add(user);
        CurrentStore.Save();
 
        status = MembershipCreateStatus.Success;
        return CreateMembershipFromInternalUser(user);
    }
    catch
    {
        throw;
    }
}

最初、CreateUser()はプライベート・アシスト・メソッドValidateUsernameとValidatePasswordを呼び出し、これらのメソッドはユーザー名とEmailがストレージ内で一意であることを決定し、パスワードが要求に合致していることを決定します.これらのチェックが成功すると、最下位ストレージ(SimpleUser)のユーザーを作成できます.
最後に、このメソッドは、作成されたユーザの詳細を含む、メンバーシップUserのインスタンスを返し、呼び出されたメンバーシップクラスに渡す必要があります.このためには、SimpleUserインスタンスのプロパティとMembershipUserのプロパティを一致させるだけです.
private MembershipUser CreateMembershipFromInternalUser(SimpleUser user)
{
    MembershipUser muser = new MembershipUser(base.Name,
        user.UserName, user.UserKey, user.Email, user.PasswordQuestion,
        string.Empty, true, false, user.CreationDate, user.LastLoginDate,
        user.LastActivityDate, user.LastPasswordChangeDate, DateTime.MaxValue);
 
    return muser;
}

 
ユーザー名、E-mailアドレス、パスワードを検証する方法を見てみましょう.まずパスワードの長さを確認し、次に正規表現でパスワードのアルファベット以外の文字の個数がMinRequiredNonAlphanumericCharacters属性設定の要求に合っているかどうかを確認し、PasswordStrengthRegularExpression属性設定の正規表現を使用してパスワード仕様を検証し、この3つのチェックでtrueに戻ります.
private bool ValidateUsername(string userName, string email, Guid excludeKey)
{
    bool IsValid = true;
 
    UserStore store = UserStore.GetStore(_FileName);
    foreach (SimpleUser user in store.Users)
    {
        if (user.UserKey.CompareTo(excludeKey) != 0)
        {
            if (string.Equals(user.UserName, userName, StringComparison.OrdinalIgnoreCase))
            {
                IsValid = false;
                break;
            }
 
            if (string.Equals(user.Email, email, StringComparison.OrdinalIgnoreCase))
            {
                IsValid = false;
                break;
            }
        }
    }
 
    return IsValid;
}
private bool ValidatePassword(string password)
{
    bool IsValid = true;
    Regex HelpExpression;
 
    // Validate simple properties
    IsValid = IsValid && (password.Length >= this.MinRequiredPasswordLength);
 
    // Validate non-alphanumeric characters
    HelpExpression = new Regex(@"\W");
    IsValid = IsValid && (HelpExpression.Matches(password).Count >= this.MinRequiredNonAlphanumericCharacters);
 
    // Validate regular expression
    HelpExpression = new Regex(this.PasswordStrengthRegularExpression);
    IsValid = IsValid && (HelpExpression.Matches(password).Count > 0);
 
    return IsValid;
}

 
 

2.ログイン時にユーザーを検証する


Membershipクラスは、ユーザが入力したパスワードをプログラムで検証する方法を提供します.この方法はLoginコントロールにも使われています.ユーザーがログインしようとするたびにValidateUser()メソッドはすべて関連されます.
public override bool ValidateUser(string username, string password)
{
    try
    {
        SimpleUser user = CurrentStore.GetUserByName(username);
        if (user == null)
            return false;
 
        if (ValidateUserInternal(user, password))
        {
            user.LastLoginDate = DateTime.Now;
            user.LastActivityDate = DateTime.Now;
            CurrentStore.Save();
            return true;
        }
        else
        {
            return false;
        }
    }
    catch
    {
        throw;
    }
}

このメソッドはストレージからユーザ情報を取得し,プライベートアシストメソッドValidateUserInternalにより比較検証を行う.ユーザー名とパスワードが正しい場合は、ユーザーの2つの関連日付値を更新し、trueを返します.
パスワード検証機能を1つの方法に個別にパッケージ化すると便利です.プログラムでこの機能が一度以上使用される可能性があるからです.非常に典型的なアプリケーションは、パスワードを変更することです.古いパスワードを検証する必要があります.
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
    try
    {
        // Get the user from the store
        SimpleUser user = CurrentStore.GetUserByName(username);
        if (user == null)
            throw new Exception("User does not exist!");
 
        if (ValidateUserInternal(user, oldPassword))
        {
            // Raise the event before validating the password
            base.OnValidatingPassword(
                new ValidatePasswordEventArgs(
                        username, newPassword, false));
 
            if (!ValidatePassword(newPassword))
                throw new ArgumentException("Password doesn't meet password strength requirements!");
 
            user.PasswordSalt = string.Empty;
            user.Password = TransformPassword(newPassword, ref user.PasswordSalt);
            user.LastPasswordChangeDate = DateTime.Now;
            CurrentStore.Save();
 
            return true;
        }
 
        return false;
    }
    catch
    {
        throw;
    }
}

 
 

3.プロバイダの残りの機能


プロバイダの初期化とユーザーの作成と検証は、プロバイダの中で最も重要であり、最も実現しにくい部分です.残りの機能は、データストレージからユーザ情報を読み出したり更新したりするために使用されるだけです.例:
public override MembershipUser GetUser(string username, bool userIsOnline)
{
    try
    {
        SimpleUser user = CurrentStore.GetUserByName(username);
        if (user != null)
        {
            if (userIsOnline)
            {
                user.LastActivityDate = DateTime.Now;
                CurrentStore.Save();
            }
            return CreateMembershipFromInternalUser(user);
        }
        else
        {
            return null;
        }
    }
    catch
    {
        throw;
    }
}