它比我前几天看的ASP.NET StartKit Commerce复杂了许多。
例如:在ASP.NET StartKit TimeTracker开始有明显的三层结构的设计。PL层,BLL层和DAL层。
同时开始在项目中引进了角色权限管理功能等等。
今天我们先讨论角色权限的实现问题。
让我们先看一角色权限设置的参考资料:
http://www.cnblogs.com/kwklover/archive/2004/06/29/19455.aspx
现在假如我们系统中有3 种角色:Service,Work,Manage
要是我们想在WebForm1.aspx禁止Service,Manage这2类角色的登陆用户访问,我们可以在Web.config文件中做下面设置:
<location path="WebForm1.aspx">
<system.web>
<authorization>
<deny roles="Service,Manage" />
<deny users="?" />
</authorization>
</system.web>
</location>
还有一种方式就是:建立三个文件夹,某一角色的人只能访问某一文件夹里的ASPX.NET文件
假如我有用户a,他有Service,Work这2种角色,假如有页面abc.aspx,它允许Work,Manage这2种角色的用户访问。
个人感觉这样设置的灵活性不好,有没有通过代码控制的方法呢?
我编写了如下代码:实现单用户可以多角色,单页面多角色访问。
让我们先看Global.asax.cs中代码,请注意看事件Application_AuthenticateRequest中的代码实现。
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Threading;
using System.Globalization;
using System.Configuration;
namespace BluepieCustomerService
{
/**//// <summary>
/// Global 的摘要说明。
/// </summary>
public class Global : System.Web.HttpApplication
{
/**//// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null;
/**//// <summary>
/// 本系统自定义的角色之一“服务人员”
/// </summary>
public const string ConstUserRoleNameService="Service";
/**//// <summary>
/// 本系统自定义的角色之一“普通工作人员”
/// </summary>
public const string ConstUserRoleNameWork="Work";
/**//// <summary>
/// 本系统自定义的角色之一“管理人员”
/// </summary>
public const string ConstUserRoleNameManage="Manage";
/**//// <summary>
/// 逗号字符串
/// </summary>
public const string ConstStringComma=",";
/**//// <summary>
/// 百分号字符串
/// </summary>
public const string ConstStringPercent="%";
/**//// <summary>
/// char 类型逗号
/// </summary>
public const char ConstCharComma=',';
/**//// <summary>
/// char 类型百分号
/// </summary>
public const char ConstCharPercent='%';
/**//// <summary>
/// 发生权限访问错误时,转向的错误提示页面
/// </summary>
public const string ConstRoleErrorPageName="RoleError.aspx?Index=-1";
/**//// <summary>
/// DB的连接字符串
/// </summary>
public const string ConstWebConfigFileKeyName_ConnectionString="ConnectionString";
public Global()
{
InitializeComponent();
}
protected void Application_Start(Object sender, EventArgs e)
{
}
protected void Session_Start(Object sender, EventArgs e)
{
}
protected void Application_BeginRequest(Object sender, EventArgs e)
{
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
}
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User!=null)
{
//用户已经通过验证
if (Request.IsAuthenticated )
{
//得到用户的角色Cookie的名称
string userRolesCookieName=FormsAuthentication.FormsCookieName;
//得到用户的角色Cookie
string currentCookieValue=Context.Request.Cookies[userRolesCookieName].Value;
//解密
FormsAuthenticationTicket currentFormsAuthenticationTicket = FormsAuthentication.Decrypt(currentCookieValue);
//得到cookie中的用户数据
string[] userData = BCSTool.StringToArray(currentFormsAuthenticationTicket.UserData,ConstCharPercent);
//取得用户的个人详细信息数组
int userId=Convert.ToInt32( userData[0]);
string userDisPlayName=userData[1];
string userName=userData[2];
string userEmail=userData[3];
//按当初加入的规则分解为数组
string [] roleArray= BCSTool.StringToArray(userData[4],ConstCharComma );
//设置当前 HTTP 安全信息
Context.User = new BCSLoginPrincipal(userId,
userDisPlayName,
userName,
userEmail,
roleArray,
HttpContext.Current.User.Identity);
}
}
}
protected void Application_Error(Object sender, EventArgs e)
{
}
protected void Session_End(Object sender, EventArgs e)
{
}
protected void Application_End(Object sender, EventArgs e)
{
}
Web 窗体设计器生成的代码#region Web 窗体设计器生成的代码
/**//// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
}
#endregion
}
}
让我们再看类BCSLoginPrincipal
实现了接口:IPrincipal,该接口定义用户对象的基本功能。
该接口有一个属性IIdentity Identity {get;},获取当前用户的标识。
一个方法bool IsInRole(string role),确定当前用户是否属于指定的角色。
using System.Security.Principal;
namespace BluepieCustomerService
{
/**//// <summary>
/// BCSLoginPrincipal 的摘要说明。
/// </summary>
public class BCSLoginPrincipal :System.Security.Principal.IPrincipal
{
private string[] _userRole;
protected IIdentity _iIdentity;
int _userId;
string _userDisPlayName;
string _userName;
string _userEmail;
/**//// <summary>
/// 类BCSLoginPrincipal的有参构造器
/// </summary>
/// <param name="iIdentity"></param>
/// <param name="userRole"></param>
public BCSLoginPrincipal(int userId,string userDisPlayName,string userName,string userEmail , string[] userRole,IIdentity iIdentity)
{
this._userId=userId;
this._userDisPlayName=userDisPlayName;
this._userName=userName;
this._userEmail=userEmail;
this._userRole=userRole;
this._iIdentity=iIdentity;
}
/**//// <summary>
/// 取得和设置登陆用户的UserID
/// </summary>
public int UserId
{
get
{
return _userId;
}
set
{
_userId=value;
}
}
/**//// <summary>
/// 取得和设置登陆用户的登陆帐号
/// </summary>
public string UserDisPlayName
{
get
{
return _userDisPlayName;
}
set
{
_userDisPlayName=value;
}
}
/**//// <summary>
/// 取得和设置登陆用户的真实姓名
/// </summary>
public string UserName
{
get
{
return _userName;
}
set
{
_userName=value;
}
}
/**//// <summary>
/// 取得和设置登陆用户的Email
/// </summary>
public string UserEmail
{
get
{
return _userEmail;
}
set
{
_userEmail=value;
}
}
/**//// <summary>
/// 取得和设置登陆用户的角色数组
/// </summary>
public string[] UserRole
{
get
{
return _userRole;
}
set
{
_userRole = value;
}
}
public IIdentity Identity
{
get
{
return _iIdentity;
}
set
{
_iIdentity = value;
}
}
/**//// <summary>
/// 实现接口方法,判断用户的角色是否合法
/// </summary>
/// <param name="role"></param>
/// <returns></returns>
public bool IsInRole(string role)
{
//用户传过来的角色字符串有可能包含多个角色,所以我们得先分解为数组
string [] roleArray = BCSTool.StringToArray(role,Global.ConstCharComma);
//取得数组长度
int roleArrayLength=roleArray.Length;
//取出数组中每一个角色与用户的现有角色做比较
for(int i=0;i<roleArrayLength;i++)
{
if( isRole( roleArray[i] ) )
{
return true;
}
}
return false;
}
/**//// <summary>
/// 与用户的现有角色做比较
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
bool isRole(string str)
{
int arrayLength=_userRole.Length;
for(int i=0;i<arrayLength;i++)
{
if ( _userRole[i]==str )
{
return true;
}
}
return false;
}
}
}
工具类,定义了一些常用方法
using System.Data;
using System.Data.SqlClient;
namespace BluepieCustomerService
{
/**//// <summary>
/// 工具类,定义了一些常用方法
/// </summary>
public class BCSTool
{
/**//// <summary>
/// 根据节点名得到WebConfig文件的节点数值
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
public static string GetWebConfigFileKeyValue(string keyName)
{
return System.Configuration.ConfigurationSettings.AppSettings[keyName];
}
/**//// <summary>
/// 分解字符串为字符串数组,分解字符为参数froamt传入
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string[] StringToArray(string str,char froamt)
{
string[] strs=str.Split(new char[]{froamt});
return strs;
}
/**//// <summary>
/// 合并字符从字符数组中,合并的格式:每个字符之间用逗号间隔
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static string ArrayToString(string[] stringArray,string format)
{
System.Text.StringBuilder sb=new System.Text.StringBuilder("");
for (int i=0;i<stringArray.Length;i++)
{
sb.Append(stringArray[i]).Append(",");
}
//去掉字符尾的","
sb.Remove(sb.Length-1,1);
return sb.ToString();
}
}
}
登陆用户详细信息类,定义了登陆用户的用户的UserId,用户的真实名称等等
using System.Collections;
namespace BluepieCustomerService
{
/**//// <summary>
/// 用户的属性类
/// </summary>
public class BCSUserDetail
{
int _userId;
string _userDisPlayName;
string _userName;
string _userEmail;
string[] _roleName;
/**//// <summary>
/// 类BCSUserDetail的构造器
/// </summary>
/// <param name="userId"></param>
/// <param name="userDisPlayName"></param>
/// <param name="userName"></param>
/// <param name="userEmail"></param>
/// <param name="roleName"></param>
public BCSUserDetail(int userId, string userDisPlayName,string userName,string userEmail,string[] roleName)
{
this._userId=userId;
this._userDisPlayName=userDisPlayName;
this._userName=userName;
this._userEmail=userEmail;
this._roleName=roleName;
}
/**//// <summary>
/// 用户的UserId
/// </summary>
public int UserId
{
get
{
return _userId;
}
set
{
_userId=value;
}
}
/**//// <summary>
/// 登陆用户的昵称,即用户登陆名
/// </summary>
public string UserDisPlayName
{
get
{
return _userDisPlayName;
}
set
{
_userDisPlayName=value;
}
}
/**//// <summary>
/// 登陆用户的真实名称
/// </summary>
public string UserName
{
get
{
return _userName;
}
set
{
_userName=value;
}
}
/**//// <summary>
/// 登陆用户的Email
/// </summary>
public string UserEmail
{
get
{
return _userEmail;
}
set
{
_userEmail=value;
}
}
/**//// <summary>
/// 登陆用户的角色名数组,一个用户可能有多个角色
/// </summary>
public string[] RoleName
{
get
{
return _roleName;
}
set
{
_roleName=value;
}
}
}
}
最后在登陆事件中,我们进行如下处理:
/// 用户登陆
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void LoginImageButton_Click( object sender, System.Web.UI.ImageClickEventArgs e)
{
//加密登陆密码
string encryptPassWord=BluepieCustomerService.BCSSecurity.Encrypt(this.UserPassWordTextBox.Text);
string loginUserName=this.UserNameTextBox.Text;
BCSUser currentLoginUser=new BCSUser();
BCSUserDetail currentLoginUserDetail=null;
currentLoginUserDetail=currentLoginUser.Login (loginUserName ,encryptPassWord);
if ( currentLoginUserDetail!=null )
{
//组成用户数据
//格式为:UserId%UserDisPlayName%UserName%UserEmail%用户的角色字符串(要是用户的角色为多个,它们之间用逗号分隔)
//字符的前半段为用户的个人基本信息,后半段为用户的角色信息
System.Text.StringBuilder sb=new System.Text.StringBuilder("");
sb.Append(currentLoginUserDetail.UserId.ToString()).Append(Global.ConstStringPercent );
sb.Append(currentLoginUserDetail.UserDisPlayName).Append(Global.ConstStringPercent );
sb.Append(currentLoginUserDetail.UserName).Append(Global.ConstStringPercent );
sb.Append(currentLoginUserDetail.UserEmail).Append(Global.ConstStringPercent );
sb.Append(BCSTool.ArrayToString(currentLoginUserDetail.RoleName,Global.ConstStringComma));
//取得用户数据
string userData=sb.ToString();
FormsAuthenticationTicket currentLoginFormsAuthenticationTicket=new FormsAuthenticationTicket(
1, //版本
loginUserName, //用户名
DateTime.Now, //Cookie 的发出时间
DateTime.Now.AddHours(1), //Cookie 的到期日期,这里设置为1个钟
false, //Cookie 是持久的,为 true;否则为 false
userData //存储在 Cookie 中的用户定义数据,这里存放的是用户的角色字符串
);
//得到加密的票据
string encrypCurrentLoginFormsAuthenticationTicket=FormsAuthentication.Encrypt(currentLoginFormsAuthenticationTicket);
//设置Cookie
HttpCookie cuurentCookie=new HttpCookie(FormsAuthentication.FormsCookieName,encrypCurrentLoginFormsAuthenticationTicket);
cuurentCookie.Expires=DateTime.Now.AddHours(1);
Response.Cookies.Add(cuurentCookie);
string returnUrl = Request.QueryString["ReturnUrl"];
if (returnUrl == null)
{
returnUrl = "/";
}
//页面转向
//不要再调用FormsAuthentication.RedirectFromLoginPage,因为它会替代刚才创建的Cookie
Response.Redirect(returnUrl);
}
else
{
//登陆失败
this.ErrorLabel.Text="登陆错误!";
}
}
现在可以在任意页面判断角色信息了
例如:页面WebForm1只允许角色Service和Manage访问,那么在其Page_Load事件中,我们写下如下代码:
{
if( !BCSSecurity.IsInRole(BCSTool.GetWebConfigFileKeyValue("Service,Manage")))
{
//转到权限出错页面
Response.Redirect(Global.ConstRoleErrorPageName,true);
}
//验证角色合格,请接着做其他事情
}
这样编写代码实现了角色的灵活控制。
让我们再看类BCSSecurity中代码
我们可以在任何页面使用BCSSecurity.GetUserId();等一系列的方法得到用户的ID,名称,Email等信息,这是不是用户状态保存的又一好办法呢?
using System.Web;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace BluepieCustomerService
{
/**//// <summary>
/// 数据安全类,定义了一些数据安全方法
/// </summary>
public class BCSSecurity
{
/**//// <summary>
/// 加密一个字符串
/// </summary>
/// <param name="cleanString"></param>
/// <returns></returns>
public static string Encrypt(string cleanString)
{
Byte[] clearBytes = new UnicodeEncoding().GetBytes(cleanString);
Byte[] hashedBytes = ((HashAlgorithm) CryptoConfig.CreateFromName("MD5")).ComputeHash(clearBytes);
return BitConverter.ToString(hashedBytes);
}
/**//// <summary>
/// 判断该角色用户的访问权
/// </summary>
/// <param name="role"></param>
/// <returns></returns>
public static bool IsInRole(String role)
{
return HttpContext.Current.User.IsInRole(role);
}
/**//// <summary>
/// 用户的UserId
/// </summary>
/// <returns></returns>
public static int GetUserId()
{
return ((BCSLoginPrincipal)System.Web.HttpContext.Current.User).UserId ;
}
/**//// <summary>
/// 登陆用户的昵称,即用户登陆名
/// </summary>
/// <returns></returns>
public static string GetUserDisPlayName()
{
return ((BCSLoginPrincipal) System.Web.HttpContext.Current.User).UserDisPlayName;
}
/**//// <summary>
/// 登陆用户的真实名称
/// </summary>
/// <returns></returns>
public static string GetUserName()
{
return (( BCSLoginPrincipal ) System.Web.HttpContext.Current.User).UserName ;
}
/**//// <summary>
/// 登陆用户的Email
/// </summary>
/// <returns></returns>
public static string GetUserEmail()
{
return ((BCSLoginPrincipal) System.Web.HttpContext.Current.User).UserEmail;
}
}
}