Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

Application Blockと連携するProviderの実装と構成ツールへの登録

Cryptography Application Blockを利用した認証パスワードのハッシュ化

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/11/09 11:00

本稿では、Enterprise Libraryの利点であるApplication Blockの連携と、構成ツールでの設定機能を解説します。

はじめに

 前回は、Enterprise Libraryで提供されていない機能を拡張する方法を示しました。本稿では、Enterprise Libraryの利点であるApplication Blockの連携と、構成ツールでの設定機能を解説します。

対象読者

 Enterprise Libraryを利用している開発者。

必要な環境

概要

 前回作成したXML Authentication Providerに、以下の機能を追加します。

  1. 他のApplication Blockとの連携
  2. Cryptography Application Blockを呼び出して、ハッシュ化したパスワードを用いた認証を行います。
  3. 構成用アセンブリの作成
  4. 作成したProviderを構成ツールに登録します。

他のApplication Blockとの連携

 前回作成したProviderは、パスワードが平文で保持されているため、セキュリティ上非常に好ましくない状態です。そのため、パスワードはハッシュ化して保持しておき、入力されたパスワードのハッシュと照合します。Enterprise LibraryではCryptography Application Blockが提供されているので、この機能を使用します。

 この動作を下図に示します。

処理の流れ
処理の流れ

 これを実現するために行うことは以下の通りです。

  • 構成情報の追加
  • ソースの修正
  • XMLファイルの修正

構成情報の追加

 まずは、通常通りにHash Providerの設定を行います。

  1. [Application]を右クリックして[New]-[Cryptography Application Block]を選択し、[Cryptography Application Block]を追加します。
  2. [Hash Providers]を右クリックして[New]-[HashAlgorithm Provider]を選択し、[HashAlgorithm Provider]を追加します。この時、さまざまなアルゴリズムを実装したクラスの一覧がダイアログで表示されるので、目的のクラス(今回は[SHA1Managed])を選択します。追加したクラスのNameプロパティが「SHA1Managed」になっていることを確認しておきます。
  3. アルゴリズムの選択
    アルゴリズムの選択

 続いて、XML Authentication ProviderがこのHash Providerを参照するように設定を行います。

 Extensionsプロパティに以下の値を追加します。

  • HashProvider
  • 対象のHash Provider名を指定します。今回は先ほど作成した「SHA1Managed」を指定します。
設定の追加
設定の追加

ソースの修正

 VS .NETで、参照設定にMicrosoft.Practices.EnterpriseLibrary.Security.Cryptographyを加えます。

 元のAuthenticateメソッドに対して変更を行います。

変更後の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メソッドを追加します。

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エンコードしたものを設定するようにします。

変更後の「authentication.xml」ファイル
<?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のクラス図
構成ツールから呼び出されるProviderのクラス図

 各クラスを簡単に説明します。

  • Provider本体(XmlAuthenticationProviderクラス)
  • 既に実装しているProviderクラスです。実際の処理を行います。
  • 構成データ(XmlAuthenticationProviderDataクラス)
  • Provider独自の構成データを保持するクラスです。Providerは、実行時に構成情報をこのクラスから取得します。今までのサンプルで用いていたCustomAuthenticationProviderDataクラスを置き換えます。
  • 構成マネージャ(XmlAuthenticationDesignManagerクラス)
  • 作成したApplication BlockやProviderを構成ツールに組み込むために、構成ツールから呼び出されるイベントを定義します。
  • 構成ツール用ノード
  • 構成ツールでのノードを表すクラスです。構成データの取得・設定と、検証や項目に対するコメントを定義します。また、構成ツールでのノード間の関連を保持します。

 行うことは以下の通りです。

  • 独自の構成情報クラスの作成
  • 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の実装でもこちらを参照するように変更します。

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属性をつけることにより、構成ツールでプロパティをどのように扱うかを指定します。
  • 他ノードの参照を保持し、変更を反映します。
  • 構成情報では他Providerの名称しか保持しないため、他ノードへの参照はノードクラスが独自に保持します。参照先ノードが変更された際にはイベントが発生するため、保持している参照や名称を更新することが可能です。
ノードクラス(抜粋)
/// <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
}

構成ツール用構成マネージャクラスの作成

 作成したノードクラスは、マネージャクラスによって構成ツールに登録されます。構成マネージャクラスでは、以下の機能を実装します。

  • 構成ツールで設定する各項目を公開し、対応する構成情報を設定します。
  • 構成ファイルを読み込む際、型名からクラスを参照できるように、XML型解決サービス(XmlIncludeTypeService)に登録します。
  • ノード作成エントリを定義します。
  • 親となるノードクラスに対して、作成したノードクラスが新規作成の選択肢として含まれるように、ノード作成サービス(NodeCreationService)に登録します。

 マネージャクラスには他にも機能を実装できますが、これは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が選択肢に現れるようになり、設定を行うことができるようになります。

作成したProviderが選択肢に現れる
作成したProviderが選択肢に現れる
設定を行える
設定を行える

最後に

 本稿では、他のApplication Blockと連携するProviderの作成方法と、作成したProviderを構成ツールから利用できるようにする方法を示しました。

 今後は、Application Blockそのものを作成する方法などを扱っていきたいと思います。

参考資料

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • にしざき(にしざき)

    Team EntLib.jp - enterpriselibrary.jp を拠点として、国内における Enterprise Library の普及に向けて活動中。

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5