はじめに
前回は、Enterprise Libraryで提供されていない機能を拡張する方法を示しました。本稿では、Enterprise Libraryの利点であるApplication Blockの連携と、構成ツールでの設定機能を解説します。
対象読者
Enterprise Libraryを利用している開発者。
必要な環境
- Visual Studio .NET 2003上で動作確認しています。
- Enterprise Library, June 2005が必要です。
概要
前回作成したXML Authentication Providerに、以下の機能を追加します。
- 他のApplication Blockとの連携
- 構成用アセンブリの作成
他のApplication Blockとの連携
前回作成したProviderは、パスワードが平文で保持されているため、セキュリティ上非常に好ましくない状態です。そのため、パスワードはハッシュ化して保持しておき、入力されたパスワードのハッシュと照合します。Enterprise LibraryではCryptography Application Blockが提供されているので、この機能を使用します。
この動作を下図に示します。
これを実現するために行うことは以下の通りです。
- 構成情報の追加
- ソースの修正
- XMLファイルの修正
構成情報の追加
まずは、通常通りにHash Providerの設定を行います。
- [Application]を右クリックして[New]-[Cryptography Application Block]を選択し、[Cryptography Application Block]を追加します。
- [Hash Providers]を右クリックして[New]-[HashAlgorithm Provider]を選択し、[HashAlgorithm Provider]を追加します。この時、さまざまなアルゴリズムを実装したクラスの一覧がダイアログで表示されるので、目的のクラス(今回は[SHA1Managed])を選択します。追加したクラスの
Name
プロパティが「SHA1Managed」になっていることを確認しておきます。
続いて、XML Authentication ProviderがこのHash Providerを参照するように設定を行います。
Extensions
プロパティに以下の値を追加します。
HashProvider
ソースの修正
VS .NETで、参照設定にMicrosoft.Practices.EnterpriseLibrary.Security.Cryptographyを加えます。
元のAuthenticate
メソッドに対して変更を行います。
public bool Authenticate(object credentials, out IIdentity identity) { // 引数に NamePasswordCredential が渡されていることを確認します ArgumentValidation.CheckForNullReference( credentials, "credentials"); ArgumentValidation.CheckExpectedType( credentials, typeof(NamePasswordCredential)); NamePasswordCredential namePasswordCredential = (NamePasswordCredential)credentials; // SecurityConfigurationView から構成情報を取得します // 「Custom Authentication Provider」として設定された場合、 // CustomAuthenticationProviderData が作成されます CustomAuthenticationProviderData providerData = (CustomAuthenticationProviderData)securityConfigurationView .GetAuthenticationProviderData(ConfigurationName); // 対象となる XML ファイルをロードします // 設定ツールでは、ファイル名を Extensions の FileName に設定します XmlDocument document = new XmlDocument(); document.Load(providerData.Extensions["FileName"]); // 設定ツールでは、ユーザ全体への XPath 式を // Extensions の Path に設定します string path = providerData.Extensions["Path"]; // 設定ツールでは、path で指定されるノードからユーザIDへの // XPath 式を Extensions の UserPath に設定します string userPath = providerData.Extensions["UserPath"]; // 設定ツールでは、path で指定されるノードからパスワードへの // XPath 式を Extensions の PasswordPath に設定します string passwordPath = providerData.Extensions["PasswordPath"]; // パスワードのハッシュ化機能を使用する場合、設定ツールでは // プロバイダ名を Extensions の Hash Provider に設定します。 string hashProviderName = providerData.Extensions["Hash Provider"]; // 各ユーザを取得し、与えられたユーザIDと // パスワードが存在するかどうかを確認します foreach (XmlNode node in document.SelectNodes(path)) { XmlNode userNode = node.SelectSingleNode(userPath); if (userNode == null || userNode.Value != namePasswordCredential.Name) continue; XmlNode passwordNode = node.SelectSingleNode(passwordPath); if (passwordNode == null || !ComparePassword(hashProviderName, namePasswordCredential.Password, passwordNode.Value)) continue; // 見つかったので、GenericIdentity オブジェクトを生成します identity = new GenericIdentity(namePasswordCredential.Name); return true; } identity = null; return false; }
次に、ComparePassword
メソッドを追加します。
// 取得したパスワードが入力されたパスワードと等しいかどうかを判定します private bool ComparePassword(string cryptographyProviderName, string inputPassword, string storedPassword) { // 暗号化機能を使用しているかどうかを取得します bool usingEncryption = cryptographyProviderName != null && cryptographyProviderName.Length > 0; if (usingEncryption) // パスワードをハッシュ化し、与えられたハッシュと比較します。 // ハッシュは BASE64 エンコードされた文字列で渡します。 return Cryptographer.CompareHash(cryptographyProviderName, inputPassword, storedPassword); else return inputPassword == storedPassword; }
XMLファイルの修正
最後に、XMLファイルのパスワードをハッシュ化したものに変更します。Cryptographer.CompareHash(string, string)
には、比較するハッシュ値をBASE64エンコードして渡すので、XMLファイルにもBASE64エンコードしたものを設定するようにします。
<?xml version="1.0" encoding="utf-8" ?> <users> <user id="test1" password="erL3DDIwCRWDnyqA5wgBT/q80Ldl1X+Ls4Oz6DWhwWstvIr3" /> <user id="test2" password="7upj8Dl4aLFxrZHAZCVrLLoU/D2qPrJe572gU/lWMmzgDmSR" /> </users>
構成用アセンブリの作成
作成したProviderを構成ツールに登録することにより、構成が簡単に行えるようになります。このために作成するクラス構造を下図に示します。
各クラスを簡単に説明します。
- Provider本体(
XmlAuthenticationProvider
クラス) - 構成データ(
XmlAuthenticationProviderData
クラス) - 構成マネージャ(
XmlAuthenticationDesignManager
クラス) - 構成ツール用ノード
CustomAuthenticationProviderData
クラスを置き換えます。行うことは以下の通りです。
- 独自の構成情報クラスの作成
- Providerが独自の構成情報を参照するように変更
- 構成ツール用ノードクラスの作成
- 構成ツール用構成マネージャクラスの作成
- アセンブリに対する構成マネージャクラスの宣言
独自の構成情報クラスの作成
これまでは、構成ツールで「Custom Authentication Provider」を選択して構成情報を設定していました。そのため、Providerからは構成情報をCustomAuthenticationProviderData
クラスとして参照していました。今回は独自の構成情報を設定するため、構成情報クラスを作成する必要があります。
構成情報の基底クラスはProviderの種類ごとに用意されていますので、AuthenticationProviderData
から継承して作成します。
[XmlRoot("authenticationProvider", Namespace=SecuritySettings.ConfigurationNamespace)] public class XmlAuthenticationProviderData: AuthenticationProviderData { #region メンバ private string fileName; private string hashProvider; #endregion #region コンストラクタ // 新規作成された際の名前を指定します public XmlAuthenticationProviderData(): base("XML ファイル 認証プロバイダ") {} #endregion #region プロパティ /// <summary> /// 対象の Provider のアセンブリ名を設定または取得します。 /// </summary> /// <remarks> /// CustomProvider 以外は対象の Provider は固定なので、 /// 設定は不要です。 /// </remarks> [XmlIgnore] public override string TypeName { get { return typeof(XmlAuthenticationProvider) .AssemblyQualifiedName; } set {} } /// <summary> /// XML ファイル名を取得または設定します。 /// </summary> [XmlAttribute("fileName")] public string FileName { get { return this.fileName; } set { this.fileName = value; } } /// ハッシュ機能を提供する Provider の名称を取得または設定します。 /// </summary> [XmlAttribute("hashProvider")] public string HashProvider { get { return this.hashProvider; } set { this.hashProvider = value; } } #endregion }
構成情報クラスでは、以下の機能を実装します。
- 構成ツールで設定する各項目を保持します。
- 構成情報を保存するため、XMLシリアル化情報を設定します。
XmlRoot
属性をつけ、各プロパティにXmlAttribute
(あるいはXmlElement
)属性をつけることにより、XMLシリアル化の際にどのように保持するかを指定します。Providerが独自の構成情報を参照するように変更
独自の構成情報クラスを作成したので、Providerの実装でもこちらを参照するように変更します。
// SecurityConfigurationView から設定情報を取得します // 独自 ProviderData を作成するため、これを期待します XmlAuthenticationProviderData providerData = (XmlAuthenticationProviderData)securityConfigurationView .GetAuthenticationProviderData(ConfigurationName); // 対象となる XML ファイルをロードします // 設定ツールでは、ファイル名を FileName に設定します XmlDocument document = new XmlDocument(); document.Load(providerData.FileName); // 設定ツールでは、ユーザ全体への XPath 式を UserPath に設定します string userPath = providerData.UserPath; // 設定ツールでは、path で指定されるノードからユーザIDへの // XPath 式を UserIDPath に設定します string userIDPath = providerData.UserIDPath; // 設定ツールでは、path で指定されるノードからパスワードへの // XPath 式を PasswordPath に設定します string passwordPath = providerData.PasswordPath; // パスワードのハッシュ化機能を使用する場合、設定ツールでは // プロバイダ名を Extensions の Hash Provider に設定します。 string hashProviderName = providerData.HashProvider;
構成ツール用ノードクラスの作成
構成情報クラスが構成情報を保持するのに対して、構成ツールでのノードそのものに対応するクラスが必要となります。
構成情報クラスでは、以下の機能を実装します。
- 構成ツールで設定する各項目を公開し、対応する構成情報を設定します。
- 検証などの構成ツールにおける振る舞いを指定します。
- 他ノードの参照を保持し、変更を反映します。
Required
属性やEditor
属性をつけることにより、構成ツールでプロパティをどのように扱うかを指定します。/// <summary> /// XmlAuthentacationProvider に関する設定のノードを表します。 /// </summary> [ServiceDependency(typeof(ILinkNodeService))] public class XmlAuthentacationProviderNode: AuthenticationProviderNode { #region メンバ // 設定データへの参照です。 private XmlAuthenticationProviderData xmlAuthenticationProviderData; // HashProvider ノードへの参照を保持しておきます。 private HashProviderNode hashProviderNode; // hashProvider ノードの削除・名称変更イベントハンドラです。 private ConfigurationNodeChangedEventHandler onHashProviderNodeRemoved; private ConfigurationNodeChangedEventHandler onHashProviderNodeRenamed; #endregion #region コンストラクタ public XmlAuthentacationProviderNode(): this(new XmlAuthenticationProviderData()) {} public XmlAuthentacationProviderNode( XmlAuthenticationProviderData xmlAuthenticationProviderData) :base(xmlAuthenticationProviderData) { this.xmlAuthenticationProviderData = xmlAuthenticationProviderData; this.onHashProviderNodeRemoved = new ConfigurationNodeChangedEventHandler( this.OnHashProviderNodeRemoved); this.onHashProviderNodeRenamed = new ConfigurationNodeChangedEventHandler( this.OnHashProviderNodeRenamed); } #endregion #region プロパティ /// <summary> /// 対象のデータ型名を取得します。 /// </summary> [Browsable(false)] public override string TypeName { get { return this.xmlAuthenticationProviderData.TypeName; } } /// <summary> /// XML ファイル名を取得または設定します。 /// </summary> [Required] [Description("XML ファイル名を取得または設定します。")] [Editor(typeof(FilteredFileNameEditor), typeof(UITypeEditor))] [FilteredFileNameEditor( "XML files(*.xml)|*.xml|All files (*.*)|*.*")] public string FileName { get { return this.xmlAuthenticationProviderData.FileName; } set { this.xmlAuthenticationProviderData.FileName = value; } } /// <summary> /// ユーザ全体への XPath 式を取得または設定します。 /// </summary> [Required] [Description("ユーザ全体への XPath 式を取得または設定します。")] public string UserPath { get { return this.xmlAuthenticationProviderData.UserPath; } set { this.xmlAuthenticationProviderData.UserPath = value; } } /// ユーザ ID への Path からの相対 XPath 式を取得または設定します。 /// </summary> [Required] [Description( "ユーザ ID への Path からの相対 XPath 式を取得または設定します。")] public string UserIDPath { get { return this.xmlAuthenticationProviderData.UserIDPath; } set { this.xmlAuthenticationProviderData.UserIDPath = value; } } /// <summary> /// パスワード への Path からの相対 XPath 式を取得または設定します。 /// </summary> [Required] [Description( "パスワードへの Path からの相対 XPath 式を取得または設定します。")] public string PasswordPath { get { return this.xmlAuthenticationProviderData.PasswordPath; } set { this.xmlAuthenticationProviderData.PasswordPath = value; } } /// <summary> /// ハッシュ機能を提供する Provider を取得または設定します。 /// </summary> [Editor(typeof(ReferenceEditor), typeof(UITypeEditor))] [ReferenceType(typeof(HashProviderNode))] [Description( "ハッシュ機能を使用する場合は、" + "提供する Provider を取得または設定します。")] public HashProviderNode HashProvider { get { return this.hashProviderNode; } set { ILinkNodeService service = GetService(typeof(ILinkNodeService)) as ILinkNodeService; Debug.Assert(service != null, "Could not get the ILinkNodeService"); // ノードへの参照を設定します。 // パラメータは「旧参照」、「新参照名」、 // 「削除イベントハンドラ」、「名称更新イベントハンドラ」 this.hashProviderNode = (HashProviderNode)service.CreateReference( this.hashProviderNode, value, this.onHashProviderNodeRemoved, this.onHashProviderNodeRenamed); // 設定データの名称も更新します。 this.xmlAuthenticationProviderData.HashProvider = string.Empty; if (this.hashProviderNode != null) { this.xmlAuthenticationProviderData.HashProvider = hashProviderNode.Name; } } } #endregion #region メソッド /// <summary> /// HashProvider ノードが削除されました。 /// </summary> /// <param name="sender">イベント送信元</param> /// <param name="e">イベントパラメータ</param> private void OnHashProviderNodeRemoved(object sender, ConfigurationNodeChangedEventArgs e) { // Provider の設定を解除します。 this.HashProvider = null; } /// <summary> /// HashProvider ノードの名称が変更されました。 /// </summary> /// <param name="sender">イベント送信元</param> /// <param name="e">イベントパラメータ</param> private void OnHashProviderNodeRenamed( object sender, ConfigurationNodeChangedEventArgs e) { // ノードは変わらないので、設定データの名称のみ変更します。 this.xmlAuthenticationProviderData.HashProvider = e.Node.Name; } #endregion }
構成ツール用構成マネージャクラスの作成
作成したノードクラスは、マネージャクラスによって構成ツールに登録されます。構成マネージャクラスでは、以下の機能を実装します。
- 構成ツールで設定する各項目を公開し、対応する構成情報を設定します。
- ノード作成エントリを定義します。
マネージャクラスには他にも機能を実装できますが、これはApplication Blockを作成する際に必要となるもので、今回は実装する必要はありません。
private static void RegisterXmlIncludeTypes( IServiceProvider serviceProvider) { IXmlIncludeTypeService xmlIncludeTypeService = ServiceHelper.GetXmlIncludeTypeService(serviceProvider); xmlIncludeTypeService.AddXmlIncludeType( SecuritySettings.SectionName, typeof(XmlAuthenticationProviderData)); } private static void RegisterNodeTypes(IServiceProvider serviceProvider) { INodeCreationService nodeCreationService = ServiceHelper.GetNodeCreationService(serviceProvider); Type nodeType = typeof(XmlAuthentacationProviderNode); // この第4引数で New メニューの中の表示名称が決まります NodeCreationEntry entry = NodeCreationEntry .CreateNodeCreationEntryWithMultiples( new AddChildNodeCommand(serviceProvider, nodeType), nodeType, typeof(XmlAuthenticationProviderData), "XML ファイル 認証プロバイダ"); nodeCreationService.AddNodeCreationEntry(entry); }
アセンブリに対する構成マネージャクラスの宣言
アセンブリに対する属性を付加して、構成ツールが構成マネージャを見つけられるようにします。
[assembly : ConfigurationDesignManager(
typeof(XmlAuthenticationDesignManager))]
導入
ソリューションをビルドすると、実行用アセンブリと構成用アセンブリが作成されます。この二つを構成ツールと同じディレクトリ(「Enterprise Libraryのインストール先\bin」)にコピーしてから、構成ツールを実行します。
すると、作成したProviderが選択肢に現れるようになり、設定を行うことができるようになります。
最後に
本稿では、他のApplication Blockと連携するProviderの作成方法と、作成したProviderを構成ツールから利用できるようにする方法を示しました。
今後は、Application Blockそのものを作成する方法などを扱っていきたいと思います。