Godtear.Security,基于CAS(Code Access Security)的权限控制(二) (转)
Posted on 2009-06-19 08:34 linFen 阅读(918) 评论(0) 编辑 收藏 举报一,Godtear.Security的权限控制设想
<!--[if !supportLists]-->1. <!--[endif]-->统一winform,webform下的权限控制方式。过去在asp.net的权限控制中多数采用对页面或者URL进行权限验证,Godtear.Security基于CAS实现对类及方法进行权限验证,使asp.net中同一页面的不同操作授予不同的权限管制。Godtear.Security采用权限申明标签(Attribute)对winform,webform中的方法或类进行权限管制申明。对于C/S,B/S混合架构的项目中,为权限控制提供了一致的方式。
<!--[if !supportLists]-->2. <!--[endif]-->过往对系统的权限控制大多数在View(视图)层或者UI上,Godtear.Security可以在包括BLL,DAL,甚至对WebService进行权限验证,所以在基于SOA架构的系统中,可以很好地在服务端进行访问权限的管制。
<!--[if !supportLists]-->3. <!--[endif]-->“关注分离”是系统良好设计的原则之一,过去在系统中对业务逻辑和权限控制的分离通常采用AOP拦截或者定义一个预先进行权限验证的页面或窗体基类。Godtear.Security采用权限申明标签(Attribute)来标记方法或类,标记执行方法或者调用类型所需要的权限。这样,在系统设计和开发的前一阶段,程序员不需要考虑任何权限控制的问题而专心于业务模型和业务逻辑。当系统在无权限管制的状态下达到所有的功能和非功能性测试后,再在所需权限管制的地方添加权限申明标签就可以实现灵活的权限控制,这对于降低系统设计和开发的复杂度,工作量很有价值。
<!--[if !supportLists]-->4. <!--[endif]-->为了给系统设计者更大的灵活性,Godtear.Security只涉及到系统中相关的权限设计,而不涉及到用户或者角色管理,所以在Godtear.Security中不提供对用户和角色的CRUD等实现,也没有确定的安全性数据库结构约束。
二,Godtear.Security的权限控制设计
<!--[if !supportLists]-->1. <!--[endif]-->依然基于角色的概念来实现权限的控制,只能给用户制定多个角色而不能给用户直接指派操作权限。
<!--[if !supportLists]-->2. <!--[endif]-->角色分为TreeRole(从属角色),TeamRole(团队角色),TreeRole是可以继承的角色,一个TreeRole的角色权限是其自身权限跟父角色的权限并集。TeamRole是不能继承的角色。一个用户可以指派多个TeamRole和一个TreeRole,一个用户所具有的权限是其所具有的角色(包括TreeRole,TeamRole)的权限的并集。系统中只能指派权限进行叠加,而不能指定拒绝的权限。
<!--[if !supportLists]-->3. <!--[endif]-->权限是指对某个权限资源进行某种操作的权力。系统设计这可以灵活地定义权限资源和所有的权限操作。例如,把系统中所有的权限资源定义为:邮件,音乐和文件。把权限操作定义为:读,写,删除,执行,审批。那么读邮件,播放音乐,删除文件等都是一种权限。对同一权限资源的多种操作可以叠加为一个权限。
<!--[if !supportLists]-->4. <!--[endif]-->被添加权限申明标签的一个类或者一个方法叫做一个权限控制点,一个权限控制点对应一个权限资源,一个权限资源可以对应多个权限控制点。语义上可能有几个不同的方法是需要权限管制的,而这几个方法是共同完成相关功能的,那么这几个方法应该对应同一个权限资源。所以,合理地规划权限控制点和权限资源需要根据项目的具体情况来决定。
三,Godtear.Security的权限控制实现
<!--[if !supportLists]-->1. <!--[endif]-->IGTIdentity用户接口
public interface IGTIdentity : IIdentity,IXmlParser
{
}IGTIdentity是系统帐户的抽象接口,系统设计者在定义帐户或用户时应该实现该接口。
<!--[if !supportLists]-->2. <!--[endif]-->IGTPrincipal身份凭证接口
public interface IGTPrincipal : IPrincipal
{
void OnPermission(PermissionEventArgs e);
bool Authentication(IDroit DroitResource);
}IGTPrincipal是系统中身份凭证的接口,附带帐户信息和权限信息以便CLR在运行时进行验证。Godtear.Seciruty中缺省的GTPrincipal实现,除非必要,系统设计者不需要另外定义实现。
<!--[if !supportLists]-->3. <!--[endif]-->IAccessCode接口
public interface IAccessCode : ICopy<IAccessCode>
{
int CodeValue { get; }
}IAccessCode表示一个权限操作,权限操作以操作值来区别。
<!--[if !supportLists]-->4. <!--[endif]-->IDroit接口
Code interface IDroit : ICopy<IDroit>, Contains<IAccessCode>
{
string Guid { get; }
string Description { get; set; }
IList<IAccessCode> AccessCodes { get; }
void AddAccessCode(IAccessCode AccessCode);
void RemoveAccessCode(IAccessCode AccessCode);
bool IsSubsetOf(IDroit Droit);
}IDroit接口表示一个权限,Guid表示了权限资源,AccessCodes表示对Guid权限资源能进行的权限操作集。
<!--[if !supportLists]-->5. <!--[endif]-->ISecurityRole接口
public interface ISecurityRole : IName
{
}ISecurityRole借口表示一个角色,TreeRole,TeamRole类表示对从属角色和团队角色的实现。
<!--[if !supportLists]-->6. <!--[endif]-->GTCodePermission类
Codepublic class GTCodePermission : IPermission, IUnrestrictedPermission
{
protected IDroit _droit;
protected bool _isUnrestricted;
public GTCodePermission(IDroit Droit)
{
_droit = Droit.Copy();
}
public GTCodePermission(PermissionState state)
{
switch (state)
{
case PermissionState.None:
break;
case PermissionState.Unrestricted:
_isUnrestricted = true;
break;
default:
throw new ArgumentException("unknown PermissionState");
}
}
public virtual IGTPrincipal Principal
{
get { return Thread.CurrentPrincipal as IGTPrincipal; }
}
public virtual IPermission Copy()
{
return new GTCodePermission(this._droit.Copy());
}
public virtual void Demand()
{
PermissionEventEnums eventType;
if (Principal == null)
{
throw new GTPermissionException("No user Authentication! please login", PermissionEventEnums.NonAuthenticate);
}
else if (!Principal.Authentication(_droit))
{
eventType = PermissionEventEnums.RefuseAccess;
}
else
{
eventType = PermissionEventEnums.Authenticated;
}
Principal.OnPermission(new PermissionEventArgs(this._droit, eventType));
}
public virtual void FromXml(SecurityElement e)
{
if (e == null)
throw new ArgumentNullException("e");
if (e.Tag != "ISBPermission")
{
throw new ArgumentException("invalid tag type");
}
if (!(e.Attributes["class"] as string).StartsWith("LYG.GodTear.Security.Permissions.GTCodePermission"))
{
throw new ArgumentException("invalid type");
}
if ((e.Attributes["version"] as string) != "1")
{
throw new ArgumentException("invalid version");
}
IDroit droit = new Droit(e.Attributes["guid"] as string);
if (e.Children != null)
{
foreach (SecurityElement se in e.Children)
{
switch (se.Tag)
{
case "accessType":
droit.AddAccessCode(new AccessCode(Convert.ToInt32(se.Text)));
break;
default:
break;
}
}
}
this._droit = droit;
}
public virtual IPermission Intersect(IPermission target)
{
if (target == null)
return null;
if (!(target is GTCodePermission))
throw new ArgumentException("target type must is GTCodePermission");
GTCodePermission permission = target as GTCodePermission;
if (this._droit.Guid.CompareTo(permission._droit.Guid) != 0)
throw new ArgumentException("Must same droit resource");
if (IsUnrestricted())
return permission.Copy();
if (permission.IsUnrestricted())
return Copy();
IDroit droit = new Droit(this._droit.Guid.ToString());
foreach (IAccessCode at in permission._droit.AccessCodes)
if (this._droit.Contains(at))
droit.AddAccessCode(at.Copy());
return new GTCodePermission(droit);
}
public virtual bool IsSubsetOf(IPermission target)
{
if (target == null)
return false;
if (!(target is GTCodePermission))
throw new ArgumentException("target type must is GTCodePermission");
GTCodePermission permission = target as GTCodePermission;
if (this._droit.Guid.CompareTo(permission._droit.Guid) != 0)
throw new ArgumentException("Must same droit resource");
if (IsUnrestricted())
return permission.IsUnrestricted();
else if (permission.IsUnrestricted())
return true;
bool contain = true;
foreach (IAccessCode at in this._droit.AccessCodes)
contain = contain && permission._droit.Contains(at);
return contain;
}
public bool IsUnrestricted()
{
return _isUnrestricted;
}
public override string ToString()
{
return ToXml().ToString();
}
public virtual SecurityElement ToXml()
{
SecurityElement se = new SecurityElement("IGTPermission");
Type type = this.GetType();
StringBuilder asmName = new StringBuilder(type.Assembly.ToString());
asmName.Replace('""', '"'');
se.AddAttribute("class", type.FullName + ", " + asmName);
se.AddAttribute("version", "1");
se.AddAttribute("guid", this._droit.Guid.ToString());
foreach (IAccessCode at in this._droit.AccessCodes)
{
SecurityElement sec = new SecurityElement("accessType");
sec.Text = at.CodeValue.ToString();
se.AddChild(sec);
}
return se;
}
public virtual IPermission Union(IPermission target)
{
if (target == null)
return Copy();
if (!(target is GTCodePermission))
throw new ArgumentException("target type must is GTCodePermission");
GTCodePermission permission = target as GTCodePermission;
if (this._droit.Guid.CompareTo(permission._droit.Guid) != 0)
//throw new ArgumentException("Must same droit resource");
return Copy();
if (IsUnrestricted() || permission.IsUnrestricted())
return new GTCodePermission(PermissionState.Unrestricted);
IDroit droit = this._droit.Copy();
foreach (IAccessCode at in permission._droit.AccessCodes)
droit.AddAccessCode(at);
return new GTCodePermission(droit);
}
}GTCodePermission是核心类,它没有象前一篇文章所列具的代码访问权限实现那样继承于CodeAccessPermission,而是直接实现IPermission和IUnrestrictedPermission接口,因为在Godtear.Security中所需要的是CAS的权限验证(Demand),而无须Deny,PermitOny等。另外在验证权限失败时候也没有抛出系统的SecurityException,而是抛出自定义的GTPermissionException异常。
<!--[if !supportLists]-->7. <!--[endif]-->CodeAccessBaseAttribute抽象基类
Code| AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
[Serializable]
public abstract class CodeSecurityBaseAttribute : CodeAccessSecurityAttribute
{
private IDroit _droit;
public IDroit Droit
{
get { return _droit; }
}
public string ResourceID
{
get { throw new NotSupportedException("this method is not ResourceID"); }
set
{
if (value != null)
{
_droit = new Droit(value);
}
}
}
List<IAccessCode> codes = new List<IAccessCode>();
protected void AddAccessCode(IAccessCode accessCode)
{
codes.Add(accessCode);
}
protected void AddAccessCodeValue(int codeValue)
{
this.AddAccessCode (new AccessCode(codeValue));
}
protected void SetAccessCodes<T>(T requiredRight) where T : struct
{
string[] names = Enum.GetNames(typeof(T));
foreach (string name in names)
{
T type = (T)Enum.Parse(typeof(T), name);
if ((Convert.ToInt32(requiredRight) & Convert.ToInt32(type)) != 0)
this.AddAccessCodeValue(Convert.ToInt32(type));
}
}
public CodeSecurityBaseAttribute(SecurityAction action)
: base(action)
{
}
public override IPermission CreatePermission()
{
foreach (IAccessCode code in codes)
this._droit.AddAccessCode(code);
GTCodePermission perm = null;
if (this.Unrestricted)
perm = new GTCodePermission(PermissionState.Unrestricted);
else
perm = new GTCodePermission(this._droit.Copy());
return perm;
}
}CodeAccessBaseAttribute类是是所有权限申明标签的基类,系统设计者可以根据自己对权限资源和权限操作的定义,简单地继承该类就实现自己的权限申明标签,Godtear.Security的Samples项目中有相关的示范。
<!--[if !supportLists]-->8. <!--[endif]-->Authentication和WebAuthentication类
Code class Authentication:IAuthenticate
{
protected bool loginAuthentication = false;
protected string username;
protected string password;
public Authentication()
{
}
public Authentication(string userName, string password)
{
loginAuthentication = true;
this.username = userName;
this.password = password;
}
protected IGTPrincipal CreatePrincipal()
{
IGTPrincipal principal = null;
if (loginAuthentication)
{
IIdentityProvider identityProvider = IdentityProviderConfig.GetIdentityProvider();
if (identityProvider == null)
throw new ConfigurationException ("Not find identity validate provider.");
IRoleProvider roleProvider = RoleProviderConfig.GetRoleProvider();
if (roleProvider == null)
throw new ConfigurationException("Not find role provider.");
IGTIdentity identity= identityProvider.GetIdentity(this.username, this.password);
if(identity !=null)
principal = new GTPrincipal(identity, roleProvider.GetTreeRole(identity), roleProvider.GetTeamRoles(identity));
}
return principal;
}
public virtual void SignIn()
{
IGTPrincipal principal = CreatePrincipal();
if (principal != null)
System.Threading.Thread.CurrentPrincipal = principal;
else if(loginAuthentication)
throw new GTSecurityException("No authentication identity!");
}
public virtual void SignOut()
{
System.Threading.Thread.CurrentPrincipal = null;
}
}
public class WebAuthentication : IAuthenticate
{
enum validateType
{
Login,
Cookie,
Null
}
public static readonly string AUTHENTICATION_COOKIE_KEY = "WebAuthentication";
public static readonly string AUTHENTICATION_COOKIE_DOMAIN = null;//".Aokoo.Net";
public static readonly int AUTHENTICATION_COOKIE_EXPIRATION = 40;
private static Dictionary<string, IGTPrincipal> _principalSet = new Dictionary<string, IGTPrincipal>();
protected static IEncryption encryption = EncryptionProviderConfig.GetEncryptionProvider();
protected HttpApplication application = null;
protected string username;
protected string password;
private validateType _validateType = validateType.Null;
public WebAuthentication()
{
}
public WebAuthentication(HttpApplication app)
{
_validateType = validateType.Cookie;
this.application = app;
}
public WebAuthentication(string userName, string password)
{
_validateType = validateType.Login;
this.username = userName;
this.password = password;
}
protected IGTPrincipal CreatePrincipal()
{
IGTPrincipal principal = null;
switch (this._validateType)
{
case validateType.Cookie:
principal = CreatePrincipalFromCookie();
break;
case validateType.Login:
IIdentityProvider identityProvider = IdentityProviderConfig.GetIdentityProvider();
if (identityProvider == null)
throw new ConfigurationException("Not find identity validate provider.");
IRoleProvider roleProvider = RoleProviderConfig.GetRoleProvider();
if (roleProvider == null)
throw new ConfigurationException("Not find role provider.");
IGTIdentity identity = identityProvider.GetIdentity(this.username, this.password);
if (identity != null)
principal = new GTPrincipal(identity, roleProvider.GetTreeRole(identity), roleProvider.GetTeamRoles(identity));
break;
case validateType.Null:
break;
}
return principal;
}
protected IGTPrincipal CreatePrincipalFromCookie()
{
IGTPrincipal principal = null;
HttpRequest request = application == null ? HttpContext.Current.Request : application.Request;
if (request.Cookies.Count > 0 && request.Cookies[AUTHENTICATION_COOKIE_KEY.ToUpper()] != null)
{
HttpCookie cookie = request.Cookies[AUTHENTICATION_COOKIE_KEY.ToUpper()];
if (cookie != null)
{
string Name =UnicodeEncoding .Unicode .GetString (encryption.Decrypt (UnicodeEncoding .Unicode .GetBytes( cookie.Value)));
_principalSet.TryGetValue(Name, out principal);
}
}
return principal;
}
protected void RemoveAuthenticationCookie()
{
System.Web.Security.FormsAuthentication.SignOut();
HttpResponse response = application == null ? HttpContext.Current.Response : application.Response;
HttpCookie cookie = response.Cookies.Get(AUTHENTICATION_COOKIE_KEY.ToUpper());
if (cookie != null)
{
cookie.Expires = new DateTime(1999, 10, 12);// DateTime.Now.AddDays(-1);
cookie.Domain = AUTHENTICATION_COOKIE_DOMAIN;
cookie.Path = System.Web.Security.FormsAuthentication.FormsCookiePath;
cookie.Secure = System.Web.Security.FormsAuthentication.RequireSSL;
response.Cookies.Add(cookie);
}
}
protected void SaveAuthentication(IGTPrincipal principal)
{
string Name = principal.Identity.Name;
_principalSet[Name] = principal;
HttpCookie userCookie = new HttpCookie(AUTHENTICATION_COOKIE_KEY.ToUpper(), UnicodeEncoding .Unicode .GetString(encryption.Encrypt(UnicodeEncoding .Unicode .GetBytes(Name))));
userCookie.Expires = DateTime.Now.AddMinutes(AUTHENTICATION_COOKIE_EXPIRATION);
userCookie.Domain = AUTHENTICATION_COOKIE_DOMAIN;
HttpResponse response = application == null ? HttpContext.Current.Response : application.Response;
response.Cookies.Add(userCookie);
}
public virtual void SignIn()
{
IGTPrincipal principal = CreatePrincipal();
if (principal != null)
{
HttpContext.Current.User = principal;
System.Threading.Thread.CurrentPrincipal = principal;
if (_validateType == validateType.Login)
SaveAuthentication(principal);
}
else if (_validateType == validateType.Login)
throw new GTSecurityException("No authentication identity!");
}
public virtual void SignOut()
{
IGTIdentity identity = HttpContext.Current.User.Identity as IGTIdentity;
if(identity !=null)
_principalSet.Remove(identity.Name);
HttpContext.Current.User = null;
System.Threading.Thread.CurrentPrincipal = null;
RemoveAuthenticationCookie();
HttpContext.Current.Session.Clear();
HttpContext.Current.Session.Abandon();
}
}Authentication和WebAuthentication类是帮助用户进行登录和登出的类,分别在Winform和Webform项目中。示范可以参考Godtear.Security的Samples项目。
<!--[if !supportLists]-->9. <!--[endif]-->IIdentityProvider 和IRoleProvider接口
IIdentityProvider 和IRoleProvider接口是系统中身份获取和角色权限获取的提供接口, Godtear.Security不关心自定义帐户及权限数据库, 系统设计者可以灵活地自定义,系统设计者只需要实现这两个接口,然后在Godtear.config中的Security配置块中配置自定义实现。示范可以参考Godtear.Security的Samples项目。
四,Godtear.Security的权限控制示范
Godtear.Security附带了Samples项目,Samples项目不是以框架设计者的角度,而是以应用系统设计者的角度来使用Godtear.Security的。
<!--[if !supportLists]-->1. <!--[endif]-->Samples.BusinessModel
Samples中的Samples.BusinessModel项目中定义了实体对象User,权限申明标签SampleCodeAccessAttribute,及应用系统中的权限资源PermissionResource枚举和权限操作RightType枚举。
Code/// <summary>
/// 权限类型
/// </summary>
public enum RightType
{
Read=1,
Write=2,
Delete=4,
Execute=8
}
/// <summary>
/// 权限资源
/// </summary>
public enum PermissionResource
{
SendEmail,
Download,
PlayMusic
}
public class SampleCodeSecurityAttribute:CodeSecurityBaseAttribute
{
public SampleCodeSecurityAttribute(SecurityAction action)
: base(action)
{
}
public new PermissionResource ResourceID
{
get { throw new NotSupportedException("this method is not ResourceID"); }
set
{
base.ResourceID = value.ToString();
}
}
public RightType RequiredRight
{
get { throw new NotSupportedException("this method is not supported"); }
set
{
SetAccessCodes<RightType>(value);
}
}
}
public class User : IGTIdentity
{
public User(string username)
{
_name = username;
_isAuthenticated = true;
}
public string AuthenticationType { get { return "SampleAuthentication"; } }
public bool IsAuthenticated
{
get
{
return _isAuthenticated;
}
}
private bool _isAuthenticated;
public string Name
{
get { return _name; }
}
private string _name;
public void FromXml(string xml)
{
}
public string ToXml()
{
return null;
}
}
<!--[if !supportLists]-->2. <!--[endif]-->Samples.DataProvider
Samples.DataProvider项目定义了IIdentityProvider,IRoleProvider,IEncrption的实现。
Code class IdentityProvider:IIdentityProvider
{
public IGTIdentity GetIdentity(string name, string password)
{
if (string.Equals(name, "lyg") && string.Equals(password, "lyg"))
return new User(name);
else
return null;
}
}
public class RoleProvider:IRoleProvider
{
public TeamRole[] GetTeamRoles(IGTIdentity identity)
{
TeamRole str = new TeamRole("管理员");
IDroit droit = new Droit(PermissionResource .Download .ToString ());
RightType requiredRight=RightType .Read |RightType .Write |RightType .Delete ;
string[] names = Enum.GetNames(typeof(RightType ));
foreach (string name in names)
{
RightType type = (RightType)Enum.Parse(typeof(RightType), name);
if ((Convert.ToInt32(requiredRight) & Convert.ToInt32(type)) != 0)
droit.AddAccessCode (new AccessCode (Convert.ToInt32(type)));
}
str.AddDroit(droit);
IList<TeamRole> list = new List<TeamRole>();
list.Add(str);
return list.ToArray<TeamRole>();
}
public TreeRole GetTreeRole(IGTIdentity identity)
{
return null;
}
}
public class AuthenticationEncryption:IEncryption
{
public byte[] Decrypt(byte[] input)
{
return input;
}
public byte[] Encrypt(byte[] input)
{
return input;
}
}由于帐户和角色的持久化结构是由用户定义的,所以Samples中没有给出相应的实现,而只是测试性的数据。应用系统设计者可以自定数据库结构和访问,xml架构和读写,甚至调用WebService来获取帐户和角色信息。对于数据库设计者来说,一个概念性的结构可能是这样的: <!--[if !vml]--><!--[endif]-->
<!--[if !supportLists]-->3. <!--[endif]-->Samples.Security
Samples.Security是Demo的Winform主程序,Godtear.config配置文件如下:
<godTear>
<dataAccess>
</dataAccess>
<providers>
</providers>
<security>
<encryptionProvider type="Samples.DataProvider.AuthenticationEncryption,Samples.DataProvider" />
<identityProvider type="Samples.DataProvider.IdentityProvider,Samples.DataProvider" />
<roleProvider type="Samples.DataProvider.RoleProvider,Samples.DataProvider" />
</security>
</godTear>
项目中对权限控制方式如下:
Code=PermissionResource .Download , RequiredRight = RightType.Read | RightType.Write | RightType.Delete)]
void DoSomething2()
{
System.Windows.Forms.MessageBox.Show("Do Something2");
}
[SampleCodeSecurity(SecurityAction.Demand , ResourceID = PermissionResource.Download, RequiredRight = RightType.Read|RightType .Execute )]
void Dosomething3()
{
System.Windows.Forms.MessageBox.Show("Do Something3");
}以上代码的语义是:执行Dosomething2方法需要具有对Download资源的Read,Write和Delete的权限,否则拒绝执行并抛出异常。执行Dosomething3方法需要具有对Download资源的Read和Execute的权限,否则拒绝执行并抛出异常。Dosomething2与Dosomething3属于两个不同的权限控制点,但却归属同一个权限资源Download,此种情况一般是因为Dosomething2与Dosomething3进行了相关的功能操作。
new Authentication("lyg", "lyg").SignIn();
new Authentication().SignOut();
分别是在winform中进行帐户的登录和登出操作。
<!--[if !supportLists]-->4. <!--[endif]-->Site"SecurityBlock.aspx
SecurityBlock.aspx是Webform的Demo页面,需要在Web.config中添加HttpModule
<httpModules>
<add name ="AuthenticationModule" type ="LYG.GodTear.Security.Authentication.AuthenticationModule,LYG.Godtear.Security"/>
</httpModules>
new WebAuthentication("lyg", "lyg").SignIn();
new WebAuthentication().SignOut();
分别是在webform中进行帐户的登录和登出操作。
总结:
Godtear.Security是Godtear开源工程的一部分,其基于CAS的权限控制相对比较灵活,方便和更深度的管制,由于比较匆忙所以不尽完善,Godtear的其他Block将在后续发布。源代码可以到Godtear工程的项目主页是godtear.codeplex.com下载。