I. background
- .. NET platform does not have a complete RBAC mechanism ,. the security model (code access security: CAS) in. NET is implemented only at the role level, but not at the task level. asp. NET 2.0, such as membership, Web. the security configurations of config can only be set for role. To use these security mechanisms, you often need to take a role in hardcode, in this way, you cannot implement the function of customizing roles during runtime.
- Although the self-contained authorization manager in Windows 2000/2003 implements a relatively complete RBAC model, it is generally only applicable to Windows users and also needs to be manually checked (the accesscheck method is called)
- Permission check is a common operation, and the best implementation method is Aspect-oriented programming (AOP)
Ii. Related Topics
- RBAC model elements: three entities: User, role, task (or operation) (user, role, and task), whose stability is gradually enhanced, user <-> role, role <-> task, where:
- User is created during daily management and operation
- Role is the deployment/delivery Establishment
- Task is determined during development
- User <-> role is created during daily management and operation.
- Role <-> task is created during deployment/delivery.
- Generally, tasks are fixed and closely bound to applications. It does not matter even if it is hard-coded.
- The user/role section is relatively easy to implement, such as the implementation of membership in ASP. NET 2.0.
Iii. Implementation
Note: The idea of Implementing AOP in this article mainly comes from the following article: Aspect Oriented Programming Using. net-AOP in C # (http://www.developerfusion.co.uk/show/5307/3/), which is what I see in. NET provides the simplest and most convenient method to implement AOP. It does not provide a brief introduction to the principles, but also provides the sample project of Visual Studio 2005, including the security check and logging AOP functions. It has the advantage that, when implementing AOP, you do not need to create an interface (this is a practice of many people) and directly make a few changes to the original class to implement the complete AOP function.
1. Define the attribute describing the "task" (task)
Using system; namespace businesslogic. security... {/** // defines the operation in the system. // [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. Write the AOP securityaspect for permission check to complete the permission check function.
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... {// Message Receiver internal class securityaspect: imessagesink... {// internal variable private imessagesink m_next; // constructor internal securityaspect (imessagesink next )... {m_next = next;} imessagesink implementation # Region imessagesink implements public imessagesink nextsink... {get... {return m_next; }}// synchronously process the message public iMessage syncprocessmessage (iMessage MSG )... {preprocess (MSG); iMessage returnmethod = parse (MSG); Return returnmethod;} // Asynchronous Message Processing (not implemented) Public imessagectrl asyncprocessmessage (iMessage MSG, imessagesink replysink )... {Throw new invalidoperationexception () ;}# endregion custom AO P method # region custom AOP method private void preprocess (iMessage MSG)... {// only process method call if (! (MSG is imethodmessage) return; // obtain the task attribute defined in the method and send it to the permission check class to check 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. getType (call. typename) ;}# endregion} public class permissioncheckproperty: icontextproperty, icontributeobjectsink... {icontributeobjectsink implementation: add the AOP class to the message processing chain # region icontributeobjectsink implementation, add the AOP class to the message processing chain public imessagesink getobjectsink (marshalbyrefobject o, imessagesink next )... {return New securityaspect (next) ;}# endregion icontextproperty implementation # region icontextproperty implementation public string name... {get... {return "permissioncheckproperty" ;}} public void freeze (context newcontext )... {} public bool isnewcontextok (context newctx )... {return true ;}# endregion} // Feature Definition, used for consumer [attributeusage (attributetargets. class)] public class permissioncheckattribute: contextattribute... {public permissioncheckattribute (): Base ("permissioncheck ")... {} public override void getpropertiesfornewcontext (iconstructioncallmessage CCM )... {ccm. contextproperties. add (New permissioncheckproperty ());}}}
?
3. Define two classes for permission check: Azman and azhelper
The functions of these two classes are to read the ing between the role and the task from the xml configuration file to determine whether the role contains the reference of the task and whether the current role has the permission for the task.
Note: Based on the actual situation of the project, if the roing between your role and task is stored in the Windows authorization manager or database, you can use your own
To replace the following classes.
In this example, the relationship between role and task is stored in the XML file. The XML file format is as follows:
<? XML version = "1.0" encoding = "UTF-8"?> <ACL> <tasks> <Task Name = "additem" Description = "add"/> <Task Name = "modifyitem" Description = "modify"/> <Task Name = "removeitem ""Description =" delete "/> <Task Name =" listitem "Description =" retrieve list "/> </tasks> <roles> <role name =" manager "> <task name = "additem"/> <Task Name = "modifyitem"/> <Task Name = "removeitem"/> <Task Name = "listitem"/> </role> </ roles> </ACL>
Azman. CS checks the ing between roles and tasks.
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 helper class helps other classes to better call Azman class methods and cache the xml configuration file of the role <--> task based on performance considerations:
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... {/** // check whether the current user has the permission to execute the current task. if the user has the permission, no processing is performed. // if the user does not have the permission, an exception is thrown. // public static void permissioncheck (string taskname )... {If (httpcontex T. 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 ("access is denied. The current user does not have permission to operate this function! ");}} /** // Check whether the current user has the permission to execute the specified task. // Task Name // true/false: whether to allow public static bool execution 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;}/** // cache the XML file // 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. Implementation of the business logic class
Most of the work is implemented in AOP, so the implementation of the business logic class is relatively simple, mainly divided into the following steps:
- Attribute: [permissioncheck ()]
- Make the class inherit from the contextboundobject object
- Use the task attribute at the method level to define the corresponding operations (Note: multiple methods can be defined as the same task)
Example: itemmanager. CS
Namespace businesslogic... {[permissioncheck ()] public class itemmanager: contextboundobject... {[task ("additem", "add")] public void additem (item )... {//...}}}
In this way, the CLR will check the permissioncheck of the class at runtime? Attribute, then find the task on the method, retrieve the corresponding role of the current user, and then perform the matching check. If this operation cannot be performed, an unauthorizedaccessexception exception will be thrown, external Processing (such as in ASP.. net)
5. Implementation of other related functions
Q: When I write a program, a large number of tasks are defined in each business logic class?
A:Reflection can be used to retrieve all tasks defined in the Assembly. The Code is as follows:
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; // check duplicate task if (DIC. indexof (TA. name) <0 )... {dic. add (TA. name); sxml. append (string. format ("\ r \ n", ta. name, ta. description) ;}}}// this is the sxml definition for all tasks. append ("\ r \ n ");}
This code is used to save the task definition to an XML file. If you want to save it to SQL Server/authorzatiom manager, modify the code slightly.
Q:How to Implement the role in the program?
A:For ASP. NET applications, you can directly use the membership role mechanism.
Q:If I want to implement some control in advance on the interface, for example, if a user cannot perform an operation, the corresponding button will be directly disabled or hidden (disable/invisible). What should I do?
A:You can use ASP. the expression function in net 2.0 directly checks whether the current user's role can execute tasks. If not, use the returned bool value to directly set the properties of controls such as button, as follows:
1) define the expression class permissioncheckexpressionbuilder. CS under app_code
[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) Add the above expression definition to Web. config so that it can be referenced directly on the page.
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <expressionBuilders> <add expressionPrefix="PermissionCheck" type="PermissionCheckExpressionBuilder"/> expressionBuilders>configuration>
3) bind an expression directly to the corresponding property of the page control, for example:
4) if you want to check permissions in the code, you can directly call the corresponding method, for example:
Protected void button#click (Object sender, eventargs e)... {azhelper. permissioncheck ("additem"); //. Other operations}
5) How to Create a roing between user <--> role and role <--> task
The former is relatively simple, and ASP. NET 2.0 already has this function. Of course, you can also use its API to implement your own definition interface.
For role-task ing, the above code is used to retrieve all tasks from the assembly and save them in the XML file. Then, the role and task can be displayed during configuration,.
As shown in:
Ing between roles and tasks
Ing between users and Roles