基于操作+角色的授权方案 思路篇
接上文,上一篇随笔说了基于操作+角色授权方案的设计思路,本随笔谈谈如何实现,可以方便高效的使用此方案。
在这套授权方案中角色是用户可配置的,而操作是死的,我们在程序中实现了什么功能就有什么样的操作,所以我们充分利用这一点,为了使用方便我们将操作作为PageBase(项目中统一的Page基类)的Attribute来定义,然后在PageBase对应页面执行PreLoad事件时读取此页面上定义的Attribute属性中的操作,然后读取当前用户可执行的操作,对比得到用户是否有访问该页面的权限。
ActionsAttribute的实现很简单,就是定义了一个只读的string数组来存放页面功能中的操作。其实现如下:
[global::System.AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public sealed class ActionsAttribute : Attribute
{
public readonly string[] PageActions;
public ActionsAttribute(params string[] actions)
{
PageActions = actions;
}
}
我们需要在PageBase类中实现对ActionsAttribute(即当前页实现操作)的读取和对权限的判断,关键代码如下
PageBase中的实现
protected override void OnInit(EventArgs e)
{
PreLoad += new EventHandler(IfNoPermission);
base.OnInit(e);
}
protected static Type _permissionAttributeType = typeof(ActionsAttribute);
protected string[] _actions = null;
private static Dictionary<Type, string[]> _pageActionsDict = new Dictionary<Type, string[]>();
protected string[] _allowedRoles = null;
protected virtual bool CanEnterPage()
{
if (_actions == null)
{
Type t = this.GetType();
if (_pageActionsDict.ContainsKey(t))
{
_actions = _pageActionsDict[t];
}
else
{
object[] attributes = t.GetCustomAttributes(_permissionAttributeType, true);
if (attributes != null && attributes.Length > 0)
{
List<string> actionList = new List<string>();
foreach (object attr in attributes)
{
ActionsAttribute actionsAttr = attr as ActionsAttribute;
if (actionsAttr != null && actionsAttr.PageActions != null && actionsAttr.PageActions.Length > 0)
{
actionList.AddRange(actionsAttr.PageActions);
}
}
_actions = actionList.ToArray();
}
else
{
_actions = new string[] { };
}
#if !DEBUG
if (_pageActionsDict.ContainsKey(t) == false)
{
_pageActionsDict.Add(t, _actions);
}
#endif
}
}
if (_actions != null && _actions.Length > 0)
{
return LoginUser.CanDoAction(_actions);
}
return true;
}
protected virtual void RedirectIfHasNoPermission()
{
Response.Redirect("~/error/permission.htm", true);
}
protected virtual void IfNoPermission(object sender, EventArgs e)
{
if (!CanEnterPage())
{
RedirectIfHasNoPermission();
}
}
大家都知道读取Attribute是通过反射来做的,其性能会有问题,所以我们生命了一个静态的成员变量_pageActionsDict来保存解析出来的Attribute和Page类型的对应字典,这样所有页面的Attribute都仅需要读取一遍,对性能几乎没有影响。
在上面的方法中我们使用了LoginUser.CanDoAction方法,其中LoginUser是PageBase的一个属性,表示当前登录的用户,在User类中有对当前用户权限的判断。其相关代码如下:
User类中判断权限部分
/// <summary>
/// 判断用户是否具有某几个角色中的一种
/// </summary>
/// <param name="roleNames">几种角色</param>
/// <returns>true拥有一种,否则没有</returns>
public bool CanDoAction(params string[] actions)
{
return true;
}
/// <summary>
/// 判断当前用户是有对某资源执行某种操作的权限
/// </summary>
/// <param name="boardId">版面id</param>
/// <param name="action">操作</param>
/// <returns>true:有权限,false:无权限</returns>
public bool CanDoAction(int boardId, string action)
{
return true;
}
这两个方法没有具体实现,不过有了设计思路实现是很简单的。
我们在具体使用中,就是直接给具体的Page类加上Attribute了,如下示例代码:
[Actions(ActionsConst.EnterAdmin)]
public partial class Default : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
其中ActionsConst是系统中所有操作的常量定义类。
全文完。