最近要做一个项目,里面有用户权限的设计这部分,以前做的都比较简单,而且是比较死板的哪种,无法进行扩展。所以为了完成这工作,就上网查了一些资料。找了很多资料,都是说了一些想法,而没有相对详细的方法。后来找到一篇,是 周晖 写的,感觉很不错,就按他的思路做了下去,下面的文章是他写的。我放在上面了。主要是留个记号。后面的一些代码,是我根据他的思路进行写的。
引言
电子商务系统对安全问题有较高的要求,传统的访问控制方法DAC(Discretionary Access Control,自主访问控制模型)、MAC(Mandatory Access Control,强制访问控制模型)难以满足复杂的企业环境需求。因此,NIST(National Institute of Standards and Technology,美国国家标准化和技术委员会)于90年代初提出了基于角色的访问控制方法,实现了用户与访问权限的逻辑分离,更符合企业的用户、组 织、数据和应用特征。ASP.NET是微软为了抗衡JSP而推出的新一代ASP(Active Server Pages)脚本语言,它借鉴了JSP的优点,同时它又具有自身的一些新特点。
本文将首先介绍ASP.NET的基本情况和RBAC(Role Based Access Control)的基本思想,在此基础上,给出电子商务系统中实现用户权限控制的一种具体方法。
ASP.NET概述
1、ASP.NET
ASP.NET是微软流行的动态WEB编程技术活动服务器网页(ASP)的最新版本,但它远不是传统ASP简单升级。ASP.NET和ASP的最大区别在于编程思维的转换,ASP.NET是真正的面向对象(Object-oriented),而不仅仅在于功能的增强。
在ASP.NET中,Web 窗体页由两部分组成:视觉元素(HTML、服务器控件和静态文本)和该页的编程逻辑。其中每一部分都存储在一个单独的文件中。可视元素在一个扩展名为 .aspx 文件中创建,而代码位于一个单独的类文件中,该文件称作代码隐藏类文件扩展名为.aspx.vb 或 .aspx.cs。这样,.aspx文件中存放所有要显示的元素,aspx.vb或.aspx.cs文件中存放逻辑。
2、用户控件(UserControl)
为了使用户能够根据需要方便地定义控件,ASP.NET引入了 Web 窗体用户控件的概念。实际上,只要将.aspx稍作修改即可转换为 Web 用户控件,扩展名为 .ascx,.ascx和.aspx文件一样也有一个存放逻辑的代码隐藏类文件,扩展名为.ascx.vb或.ascx.cs,只是它不能作为独立 Web 窗体页来运行,只有当被包含在 .aspx文件中时,用户控件才能工作。
通过以下两个步骤在WEB窗体页中设置用户控件:
(1)使用@ Register指令在.aspx文件中注册用户控件。如要注册在放在相对路径“../UserControl/”下的头文件headinner.ascx的方法为:
<%@ Register TagPrefix="Acme" TagName="Head" Src="../UserControl/headinner.ascx" %>
(2)在服务器控件的开始标记和结束标记之间(
) 声明该用户控件元素。例如要声明上面所导入的控件的语法为:
这样,该控件就成为页的一部分,并将在处理该页时呈现出来。并且,该控件的公共属性、事件和方法将向 Web 窗体页公开并且可以通过编程来使用。根据这个原理,就可以将每个页面初始化时所要执行的操作(如登录验证,角色验证)封装在用户控件当中。
RBAC的基本思想
RBAC(角色访问控制)的基本思想可简单地用图1来表示,即把整个访问控制过程分成两步:访问权限与角色相关联,角色再与用户关联,从而实现了用户与访问权限的逻辑分离。
由于RBAC实现了用户与访问权限的逻辑分离,因此它极大的方便了权限管理。例如,如果一个用户的职位发生变化,只要将用户当前的角色去掉,加入代表新 职务或新任务的角色即可,角色/权限之间的变化比角色/用户关系之间的变化相对要慢得多,并且委派用户到角色不需要很多技术,可以由行政管理人员来执行, 而配置权限到角色的工作比较复杂,需要一定的技术,可以由专门的技术人员来承担,但是不给他们委派用户的权限,这与现实中情况正好一致。
用户权限在.NET中的设计与实现
利用.NET中的用户控件实现权限控制的基本思想是:根据角色访问控制(RBAC)的基本原理,给用户分配一个角色,每个角色对应一些权限,然后利用ASP.NET中的用户控件(UserControl)来判断该用户对应的角色是否对访问页面有访问的权力。
下面将从数据库设计、添加角色和用户控件的使用等三方面来阐述具体实现过程。
1、数据库中表的设计
首先,在数据库中设计功能模块表、功能表和角色表等三个表。
(1) 功能模块表
为了管理好用户的权限,首先要组织好系统的模块,为此设计了一个功能模块表。见表1。
(2) 功能表
每个功能模块所具有的子功能称为功能,如商品管理模块goods(属于功能模块的范畴)包含商品信息查询、商品信息更新、商品信息删除、商品定价信息查询以及商品定价信息更新五种功能,功能表的设计见表2。
上面提到的例子可以作为这样几条记录分别插入功能模块表和功能表。
insert into TModule values(0,'商品管理模块','goods',5);
insert into Tfunction values(0,'商品信息查询','selectgoods',0);
insert into Tfunction values(1,'商品信息更新','updategoods',0);
insert into Tfunction values(2,'商品信息删除','deletegoods',0);
insert into Tfunction values(3,'商品定价信息查询','selectgoodsprice',0);
insert into Tfunction values(4,'商品定价信息更新','updategoodsprice',0);
(3) 角色表
角色表的设计关键在于角色值的定义,它是一个由0和1组成的类似二进制数的字符串。而功能表中的funcNo (功能编号)字段表示该功能在角色表的roleValue (角色值)字段中的位置,如果该位置对应的数值是0,表示该角色无此权限,如果值为1,则表示该角色拥有此权限。如角色普通会员的角色值为100100… 00(共100位),如上所示,商品信息查询的功能编号为0,角色值100100…00的第0位为1,所以该普通会员角色拥有商品信息查询的功能;相反, 该角色值的第1位为0,而功能编号为1 的功能为商品信息更新,所以该普通会员角色没有商品信息更新的权限。它们的关系可由图2来表示。
2、角色的添加
有了上面几个表,角色页面的功能模块以及其对应的功能都可以从功能模块表和功能表中读出,如图3所示。
在将新角色普通会员插入数据库时,先将角色值的所有位都置为0,然后利用.NET Framework 类库中的Replace函数将角色值中的打上勾的功能相应的功能编号位的值改为1。
例如,新添加一个角色名为普通会员的角色,它拥有的功能为商品信息查询(功能编号0)和商品定价信息查询(功能编号3)两项,则角色值应为1001000……00(100位),即角色值中第0位和第3位的值为1,其余为0。
3、利用用户控件实现访问权限
在定义好用户控件.ascx文件(head.ascx)及.ascx.cs(head.ascx,cs)文件时,接下去只要在.aspx文件中注册和声明它就可以了。
(1) 注册
(2) 声明
经过实践,在.aspx文件中声明.ascx文件可分为几种情况:
第一种情况:
第二种情况:
第三种情况:
字段flag是用来控制怎样进行权限检查的标志,funcname指功能表中的功能英文名。如果flag为空,则不执行权限检查(第一种情况);否则如 果flag=="0",则表示同时具有selectgoods(商品信息查询)和 updategoods(商品信息更新)这两种权限的角色所对应的用户才有权利查看该页(第二种情况);否则,如果flag=="1",则认为,具有 selectgoods(商品信息查询)或 updategoods(商品信息更新)这两种权限中任意一种权限的用户就有权利查看该页(第三种情况)。
上面进行权限检查的过程全部由用户控件来实现,其全部方法都封装在.ascx.cs文件中,其中最主要的一个方法是检查某一角色是否拥有某一确定权限的 checkAuth(string roleId,string funcEName)方法。这个方法的思想如图4所示。
图4中roleValue(角色值)的第0位(selectgoods的功能编号)值为1,表示该角色拥有selectgoods(商品信息查询)的权 限。这样,我们把对权限检查的所有逻辑都封装在了用户控件中,因此,对WEB窗体页.aspx文件而言,只需在导入.ascx文件时确定用户在访问该页面 时所应拥有的权限,而不需对aspx.cs进行任何改动。
由上所述,可以很清楚地看出,只要在用户控件中对用户权限进行控制,再把它包括在.aspx文件中(这件事作者本来就是要做的),那么在编程的时候就不必考虑复杂的权限问题了。
一,关于,datalist嵌套datalist的问题
如果光是简单的嵌套,是没有什么难度的,这个网上很多相关的例子,这里难的是,在子 datalist里,有一个checkbox控件,你要选择用户所能完成的功能时,它要传值回来。下面是我的代码。
这里的代码全是简写的。
前台:
<
form id
=
"
Form1
"
method
=
"
post
"
runat
=
"
server
"
>
<
table width
=
"
80%
"
border
=
"
1
"
cellpadding
=
"
1
"
cellspacing
=
"
1
"
>
<
tr
>
<
td
>
<
asp:DataList id
=
"
DataList1
"
runat
=
"
server
"
RepeatDirection
=
"
Horizontal
"
>
<
ItemTemplate
>
<%
# DataBinder.Eval(Container.DataItem,
"
moduleName
"
)
%>
<
asp:DataList id
=
"
Datalist2
"
runat
=
"
server
"
RepeatDirection
=
"
Horizontal
"
RepeatColumns
=
"
2
"
>
<
ItemTemplate
>
<
asp:CheckBox ID
=
"
chk
"
Text
=
'
<%# DataBinder.Eval(Container.DataItem,"funcName")%>
'
Runat
=
"
server
"
/>
<
asp:Label ID
=
"
lb
"
Text
=
'
<%# DataBinder.Eval(Container.DataItem,"funcNo")%>
'
Runat
=
"
server
"
Visible
=
"
False
"
/>
ItemTemplate
>
asp:DataList
>
ItemTemplate
>
asp:DataList
>
td
>
tr
>
table
>
<
asp:Button id
=
"
Button1
"
style
=
"
Z-INDEX: 101; LEFT: 112px; POSITION: absolute; TOP: 120px
"
runat
=
"
server
"
Text
=
"
ȡֵ
"
>
asp:Button
>
form
>
后台代码:
public
SqlConnection conn
=
new
SqlConnection(
"
server=.;database=ceshi;uid=sa;pwd=chen123
"
);
private
void
Page_Load(
object
sender, System.EventArgs e)
...
{
// 在此处放置用户代码以初始化页面
if(!Page.IsPostBack)
...{
SqlCommand cmd = new SqlCommand("",conn);
cmd.CommandText = "select * from TModule";
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = cmd;
DataSet ds = new DataSet();
da.Fill(ds,"TModule");
this.DataList1.DataSource = ds.Tables[0];
this.DataList1.DataKeyField = "innerid";
this.DataList1.DataBind();
}
}
Web 窗体设计器生成的代码
#region Web 窗体设计器生成的代码
override protected void OnInit(EventArgs e)
...{
//
// CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
//
InitializeComponent();
base.OnInit(e);
}
/**////
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
///
private void InitializeComponent()
...{
this.DataList1.ItemDataBound += new System.Web.UI.WebControls.DataListItemEventHandler(this.DataList1_ItemDataBound);
this.Button1.Click += new System.EventHandler(this.Button1_Click);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
private
void
DataList1_ItemDataBound(
object
sender, System.Web.UI.WebControls.DataListItemEventArgs e)
...
{
if(e.Item.ItemType == ListItemType.Item||e.Item.ItemType == ListItemType.AlternatingItem)
...{
DataList dataList = (DataList)e.Item.FindControl("Datalist2");
DataRowView rowv = (DataRowView)e.Item.DataItem;
int mainID = Convert.ToInt32(rowv["moduleNo"]);
//if(mainID>0)
//{
SqlCommand cmd = new SqlCommand("",conn);
cmd.CommandText = "select * from Tfunction where moduleNo="+mainID+"";
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = cmd;
DataSet ds = new DataSet();
da.Fill(ds,"Tfunction");
dataList.DataSource = ds.Tables[0];
dataList.DataKeyField = "id";
dataList.DataBind();
//}
//else
//{
//Response.Write("");
//}
}
}
private
void
Button1_Click(
object
sender, System.EventArgs e)
...
{
string id = "";
string str = "000000";
foreach(DataListItem dl1 in this.DataList1.Items)
...{
DataList datalist = (DataList)dl1.FindControl("DataList2");
foreach(DataListItem dl2 in datalist.Items)
...{
CheckBox cb = (CheckBox)dl2.FindControl("chk");
Label lbl = (Label)dl2.FindControl("lb");
if(cb.Checked)
...{
id =id + lbl.Text + ",";
//Response.Write(id);
//下面这段的功能主是对选中的项目,把它的的值0改成1
str = str.Substring(0,Convert.ToInt32(lbl.Text)) + "1" +str.Substring(Convert.ToInt32(lbl.Text)+1);
}
}
}
int idlenght = id.Length;
id = id.Remove(idlenght-1,1);
//Response.Write(id);
//str = str.Substring(0,num-1) + "1" +str.Substring(num);
//string aa = str.Substring(num,1);
Response.Write(str);
}
以上代码,如果你看不明白,请先看一下上面的文章,这里的代码,只是对上面文章的一个验证。
下面是用户控件的使用了。前台页面是aa.aspx这个页面主要的功能就是接收这个用户控件,通过控件来确定是否可以访问这个页面。
<
form id
=
"
Form1
"
method
=
"
post
"
runat
=
"
server
"
>
<
uc1:WebUserControl1 id
=
"
WebUserControl11
"
funcName
=
"
selectgoods
"
runat
=
"
server
"
>
uc1:WebUserControl1
>
form
>
这里的funcName这个变量,主是要用来确定此页面的功能,这个页面是做什么用的,是显示,修改,还是删除
后台:这里的后台是用户自定义的控件的代码
private
string
_funcName
=
""
;
public
string
funcName
...
{
get
...{
return _funcName;
}
set
...{
_funcName = value;
}
}
private
void
Page_Load(
object
sender, System.EventArgs e)
...
{
// 在此处放置用户代码以初始化页面
string fName = funcName;
string strNum = "001010";
int nowNum = 0;;
if(fName=="selectgoods")
...{
nowNum = 4;
}
else
...{
Response.Write("");
}
string name = strNum.Substring(nowNum,1);
if(name.Equals("1"))
...{
Response.Write("");
}
else
...{
Response.Write("");
}
}
好了,我相信,程序写到这份上,都应该可以看明了。呵呵。。。。。