AOPにより実現する.NET上の完全なロールベースアクセス制御(RBAC)モデル

52541 ワード

一.背景
  • .NETプラットフォームには完全なRBACメカニズムがない.NETのセキュリティモデル(コードアクセスセキュリティ:CAS)はRole階層に実装されているだけで、Task階層に細分化されていません.ASP.NET 2.0の多くのセキュリティメカニズム、例えばMembership、Web.Configのセキュリティ構成は、すべてRoleに対してしか設定できません.これらのセキュリティメカニズムを利用して、プログラム/コードハードコーディング(HardCode)ロールを必要とすることが多く、実行中にロールをカスタマイズする機能
  • を実現できません.
  • Windows 2000/2003に付属のAuthorization Managerは比較的完全なRBACモデルを実現しているが、一般的にはWindowsユーザーのみに適用され、手動でアクセスチェック(AccessCheckメソッド呼び出し)
  • を行う必要がある.
  • 権限チェックは汎用的な操作であり、最良の実現方法は側面向けのプログラミング(AOP)
  • である.
    二、関連テーマの紹介
  • RBACモデルの要素:ユーザー、ロール、タスク(またはアクション)(User、Role、Task)の3つのエンティティで、安定性が徐々に向上し、2つの関係があります.
  • Userは、日常管理運転時に確立する
  • である.
  • Roleは、導入/配信確立
  • です.
  • Taskは開発時確定
  • である.
  • User<->Roleは、日常的な管理実行時に確立された
  • です.
  • Role<->Taskは、導入/提供時に
  • を確立します.
  • 一般的に、Taskは固定的であり、アプリケーションと密接にバインドされており、ハードコーディングを行っても
  • には関係ない.
  • User/Role部分は比較的容易に実現できる、例えばASP.NET 2.0におけるMembershipの実現
  • 三、具体的な実現
    注:本文の中でAOPを実現する構想は主に以下の文章から来ている:Aspect Oriented Programming using.NET-AOP in C#(http://www.developerfusion.co.uk/show/5307/3/)、これは私が見た、あります.NET上でAOPを実現する最も簡便/便利な方法は、原理紹介を提供するのに不便であり、Visual Studio 2005のSample Projectも提供され、その中にはSecurity CheckとLoggingのAOP機能がある.その利点は、AOPを実現すると同時に、インタフェースを確立する必要がなく(これは多くの人のやり方である)、既存のクラスで直接少量の変更を行うことで、完全なAOP機能を実現できることである.
    1.タスクを記述するAttributeを定義する
    using System;

    namespace BusinessLogic.Security
    {
    ///

    ///
    ///
    [AttributeUsage(AttributeTargets.All,AllowMultiple=false,Inherited=true)]
    public sealed class Task : Attribute
    {
    private string _name,_description;

    public string Name
    {
    get { return _name; }
    set { _name = value; }
    }


    public string Description
    {
    get { return _description; }
    set { _description = value; }
    }


    public Task(string name,string description)
    {
    _name
    = name;
    _description
    = description;
    }


    public Task()
    {
    }

    }

    }

    2. 编写权限检查的 AOP 类 SecurityAspect,完成权限检查的功能

    
     
       
    using System;
    using System.Diagnostics;
    using System.Reflection;
    using System.Runtime.Remoting.Messaging;
    using System.Runtime.Remoting.Contexts;
    using System.Runtime.Remoting.Activation;

    namespace BusinessLogic.Security
    {
    //
    internal class SecurityAspect : IMessageSink
    {
    //
    private IMessageSink m_next;

    //
    internal SecurityAspect(IMessageSink next)
    {
    m_next
    = next;
    }


    IMessageSink IMessageSink public IMessageSink NextSink { get { return m_next; } } // public IMessage SyncProcessMessage(IMessage msg) { Preprocess(msg); IMessage returnMethod = m_next.SyncProcessMessage(msg); return returnMethod; } // ( ) public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink) { throw new InvalidOperationException(); } #endregion


    AOP AOP private void Preprocess(IMessage msg) { // if (!(msg is IMethodMessage)) return; // Task , IMethodMessage call = msg as IMethodMessage; MethodBase mb = call.MethodBase; object[] attrObj = mb.GetCustomAttributes(typeof(Task), false); if (attrObj != null) { Task attr = (Task)attrObj[0]; if(!string.IsNullOrEmpty(attr.Name)) AzHelper.PermissionCheck(attr.Name); } // Type type = Type.GetType(call.TypeName); } #endregion

    }



    public class PermissionCheckProperty : IContextProperty, IContributeObjectSink
    {
    IContributeObjectSink , AOP IContributeObjectSink , AOP public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink next) { return new SecurityAspect(next); } #endregion


    IContextProperty IContextProperty public string Name { get { return "PermissionCheckProperty"; } } public void Freeze(Context newContext) { } public bool IsNewContextOK(Context newCtx) { return true; } #endregion
    }


    // , Consumer
    [AttributeUsage(AttributeTargets.Class)]
    public class PermissionCheckAttribute : ContextAttribute
    {
    public PermissionCheckAttribute() : base("PermissionCheck") { }
    public override void GetPropertiesForNewContext(IConstructionCallMessage ccm)
    {
    ccm.ContextProperties.Add(
    new PermissionCheckProperty());
    }

    }

    }


    ?
    3.権限チェックに使用する2つのクラスを定義します:AzMan、AzHelper
    この2つのクラスの機能は、XMLプロファイルからRoleとTaskのマッピング関係を読み込み、RoleにTaskの参照が含まれているかどうかを決定し、現在のRoleがTaskに対する権限を持っているかどうかを決定することです.
    注意:ここでは、プロジェクトの実際の状況に応じて、RoleとTaskのマッピング関係がWindowsのAuthorizatiom Managerまたはデータベースに格納されている場合は、以下のクラスを独自の方法で置き換えることができます.
    この例では、私のロールとTaskの関係はXMLファイルに格納され、XMLファイルのフォーマットは次のようになります.
    
     
       
    <? xml version="1.0" encoding="utf-8" ?>
    < ACL >
    < Tasks >
    < Task Name ="AddItem" Description =" " />
    < Task Name ="ModifyItem" Description =" " />
    < Task Name ="RemoveItem" Description =" " />
    < Task Name ="ListItem" Description =" " />
    </ Tasks >
    < Roles >
    < Role Name ="Manager" >
    < Task Name ="AddItem" />
    < Task Name ="ModifyItem" />
    < Task Name ="RemoveItem" />
    < Task Name ="ListItem" />
    </ Role >
    </ Roles >
    </ ACL >

    
     
       


    AzMan.csロール/タスクマッピング関係のチェックを完了
    
     
       
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;

    namespace BusinessLogic.Security
    {
    public class AzMan
    {
    public static bool AccessCheck(string taskName, string[] roles, XmlDocument aclDoc)
    {
    XmlNode rootNode
    = aclDoc.DocumentElement;
    XmlNodeList roleNodes,taskNodes;
    bool IsPermissiable = false;

    for (int i = 0; i < roles.Length; i++)
    {
    roleNodes
    = rootNode.SelectNodes("Roles/Role[@Name='" + roles[i] + "']");

    if (roleNodes != null)
    {
    taskNodes
    = roleNodes.Item(0).SelectNodes("Task[@Name='" + taskName + "']");
    if (taskNodes.Count != 0)
    {
    IsPermissiable
    = true;
    break;
    }

    }

    }


    return IsPermissiable;
    }

    }

    }


    AzHelper.csアシスタントクラス、他のクラスを支援し、AzManクラスをよりよく呼び出す方法、およびパフォーマンスの考慮に基づいて、Role<-->TaskのXMLプロファイルをキャッシュします.
    
     
       
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;
    using System.Web;
    using System.Web.Security;
    using System.Diagnostics;
    using System.Reflection;
    using System.Web.Caching;


    namespace BusinessLogic.Security
    {
    public class AzHelper
    {

    ///
    /// , ,
    ///
    ///

    public static void PermissionCheck(string taskName)
    {
    if (HttpContext.Current != null)
    {
    XmlDocument aclDoc
    = (XmlDocument)HttpContext.Current.Cache["ACLDoc"];

    if (aclDoc == null)
    {
    CacheXml();
    aclDoc
    = (XmlDocument)HttpContext.Current.Cache["ACLDoc"];
    }


    string[] roles = Roles.GetRolesForUser();

    if (!AzMan.AccessCheck(taskName, roles, aclDoc))
    throw new UnauthorizedAccessException(" , !");
    }


    }


    ///
    ///
    ///
    ///

    /// True/False
    public static bool IsPermissible(string taskName)
    {
    if (HttpContext.Current != null)
    {
    XmlDocument aclDoc
    = (XmlDocument)HttpContext.Current.Cache["ACLDoc"];

    if (aclDoc == null)
    {
    CacheXml();
    aclDoc
    = (XmlDocument)HttpContext.Current.Cache["ACLDoc"];
    }


    string[] roles = Roles.GetRolesForUser();

    aclDoc.Load(HttpContext.Current.Server.MapPath(
    "~/App_Data/ACL.xml"));

    return AzMan.AccessCheck(taskName, roles, aclDoc);
    }

    else return true;
    }


    ///
    /// XML
    ///
    private static void CacheXml()
    {
    string fileName = HttpContext.Current.Server.MapPath("~/App_Data/ACL.xml");
    XmlDocument aclDoc
    = new XmlDocument();
    aclDoc.Load(fileName);
    HttpContext.Current.Cache.Insert(
    "ACLDoc", aclDoc, new CacheDependency(fileName));
    }

    }

    }


    4.ビジネスロジッククラスの実現
    ほとんどの仕事はAOPで実現されているので、ビジネスロジッククラスの実現は簡単で、主に以下のいくつかのステップに分けられます.
  • クラスの階層定義AOP方式の権限チェックを要求するAttribute:[permissionCheck()]
  • クラスをContextBoundObjectオブジェクト
  • から継承する
  • メソッド階層でTask Attributeを使用して対応するアクションを定義する(注:複数のメソッドは同じTaskとして定義できる)
  • .
    例:ItemManager.cs
    
     
       
    namespace BusinessLogic
    {
    [PermissionCheck()]
    public class ItemManager : ContextBoundObject
    {
    [Task(
    "AddItem"," ")]
    public void AddItem(Item item)
    {
    //...
    }

    }

    }




    これでいいです.CLRは実行時にクラスのPermissionCheckをチェックしますか?Attribute、そしてメソッド上のTaskを探し、現在のユーザー対応のRoleを取り出してマッチングチェックを行い、それができない場合はUnauthorizedAccessExceptionの異常を投げ出し、外部で処理すればよい(ASP.NETにErrorPageを追加するなど)
    5.その他の関連機能の実現
    Q.もし私がプログラムを書く時、各業務ロジッククラスで大量のTaskを定義したら、統一的に抽出したら?
    A:反射によってプログラムセットに定義されたすべてのTaskを取り出すことができる.コードは以下の通りである.
    
     
       
    List < string > dic = new List < string > ();

    StringBuilder sXml
    = new StringBuilder( " " );

    string curDir = this .GetCurrentPath();
    Assembly ass
    = Assembly.LoadFile(curDir + " \\AppFramework.BusinessLogic.dll " );

    foreach (Type t in ass.GetTypes())
    {
    MethodInfo[] mis
    = t.GetMethods();
    foreach (MethodInfo mi in mis)
    {
    object[] attrs = mi.GetCustomAttributes(false);
    if (attrs.Length > 0)
    {
    foreach (object attr in attrs)
    {
    if (attr.GetType().ToString().IndexOf("Task") >= 0)
    {
    Task ta
    = (Task)attr;
    // Task
    if (dic.IndexOf(ta.Name) < 0)
    {
    dic.Add(ta.Name);
    sXml.Append(
    string.Format("\r
    ", ta.Name, ta.Description));
    }

    }

    }

    }

    }

    // Task
    sXml.Append("\r
    ");
    }




    このコードは、Task定義をXMLファイルに保存します.SQL Server/Authorzatiom Managerに保存する場合は、コードを少し変更すればいいです.
    Q:プログラムのロールはどのように実現しますか?
    A:ASPならNETアプリは、その中のMemberShip Roleメカニズムを直接利用できるのか、それとも簡単なのか
    Q.あるユーザーが操作できない場合、対応するButtonを直接禁止または非表示にしたい場合は、どのようにしますか?
    A:これはASPを利用できます.NET 2.0の式機能は、現在のユーザーの役割がTaskを実行できるかどうかを直接チェックし、できない場合は、返されたBool値を利用してButtonなどのコントロールの属性を直接設定します.
    1)App_Codeの下で式クラスを定義します.cs
    
     
       
    [ExpressionEditor( typeof (PermissionCheckExpressionBuilderEditor))]
    [ExpressionPrefix(
    " PermissionCheck " )]
    public class PermissionCheckExpressionBuilder : ExpressionBuilder
    {
    public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
    {
    string taskName = entry.Expression;
    return new CodePrimitiveExpression(AzHelper.IsPermissible(taskName));
    }

    }


    public class PermissionCheckExpressionBuilderEditor : System.Web.UI.Design.ExpressionEditor
    {
    public override object EvaluateExpression(string expression, object parseTimeData, Type propertyType, IServiceProvider serviceProvider)
    {
    //return expression + ":" + parseTimeData + ":" + propertyType + ":" + serviceProvider;
    string taskName = expression;
    return AzHelper.IsPermissible(taskName);
    }

    }




    2)Web.Configに上記の式定義を追加し、ページで直接参照できるようにします.
    
     
       
    < configuration xmlns ="http://schemas.microsoft.com/.NetConfiguration/v2.0" >
    < expressionBuilders >
    < add expressionPrefix ="PermissionCheck" type ="PermissionCheckExpressionBuilder" />
    expressionBuilders>
    configuration>


    3)次のように、ページコントロールの対応する属性に直接式をバインドします.
  • この操作が実行可能であれば表示する、そうでなければ
        
       
         
    < asp:Button ID ="Button1" runat ="server" Text ="AddItem" Visible ="<%$ PermissionCheck:AddItem %>" />

  • を隠す.
  • この操作が実行可能であれば有効である、そうでなければ
        
       
         
    < asp:Button ID ="Button2" runat ="server" Text ="AddItem" Enabled ="<%$ PermissionCheck:AddItem %>" />

  • を禁止する.
    4)コード内で権限をチェックする場合は、次のような方法を直接呼び出すことができます.
    
     
       
    protected void Button1_Click( object sender, EventArgs e)
    {
    AzHelper.PermissionCheck(
    "AddItem");

    //..
    }



    5)どのようにUser<-->Roleのマッピングを創立して、Role<-->Taskのマッピング
    前者は比較的簡単で、ASP.NET 2.0にはすでにこの機能があり、もちろんAPIを利用して自分の定義インタフェースを実現することもできます.
    Role-Taskのマッピングでは,まず上のコードを用いてプログラムセットからすべてのTaskを取り出し,XMLファイルに保存し,次に構成時にRoleとTaskを表示してマッピングを行う.
    次の図に示します.
    ロールとタスクのマッピングユーザーとロールのマッピング