.NET安全系列之三:用户与角色的概念/基于角色的安全

 

http://www.cnblogs.com/lsxqw2004/archive/2009/01/22/1379989.html

继上篇总结了CAS,这篇来总结一下与用户与角色相关的安全话题。每个用户都可以 访问系统中他们各自的账户,这体现了一个用户的概念。一个系统还应按类型区分它的用户,一个用户类型即为一个角色。系统中的用户属于零个、一个或多个角 色。在应用程序中授予用户何种级别的信任主要取决于用户所扮演的角色,系统需要在用户执行某个关键操作前验证其角色。

Windows用户方面的安全策略

在Windows中也实现了用户 与角色的概念,但Windows中的角色是通过用户组 来实现的。每个用户组有一个角色与之对应,将用户加入某一用户组后,该用户即获得了该用户组对应角色所拥有的权限。一个用户可以属于一个或多个用户组。

在一个Windows NT系统中每个运行的进程都有一个与其关联的表示当前用户身份的被称为主体的东西(主体标识了用户及其所属角色)。一个线程执行在其进程的主体的安全上下 文中(也可以用重载过的Process.Start()方法创建一个与其父进程不在同一安全上下文的新进程)。例外情况是系统启动时创建的三个登录会话: 系统会话、本地会话以及网络会话没有关联的用户主体。这些会话为一些自启动的服务所准备。

Windows为其中的各种资源关联一系列的访问规则。当一个线程访问一个Windows资源时,Windows会验证与该线程关联的主体是否符合访问规则的要求。

.NET用户方面安全介绍

.NET对Windows API函数进行了封装,使我们可以在编程时使用Windows安全系统。

.NET Framework中提供了IIdentity与IPrincipal接口分别表示身份主体 的概念。

IIdentity接口用于身份认证,其定义如下:

interface System .Security.Principal.IIdentity

{

string AuthenticationType{get ;}

bool IsAuthenticationd{get ;}

string Name{get ;}

}

IPrincipal接口用于权限管理,其定义

interface System .Security.Principal.IPrincipal

{

IIdentity IIdentity{get ;}

bool IsInRole(string role);

}

System.Security.Principal.WindowsIdentity与 System.Security.Principal.WindowsPrincipal分别实现了IIdentity与IPrincipal接口,可以 用这两个类来操作Windows安全。

示例:以下代码获得当前线程运行上下文的用户信息。

using System;

using System.Security.Principal;

class Program {

static void Main() {

IIdentity id = WindowsIdentity .GetCurrent();

Console .WriteLine( "Name : " + id.Name );

Console .WriteLine( "Authenticated? : " + id.IsAuthenticated );

Console .WriteLine( "Authentication Type : " +id.AuthenticationType );

}

}

.NET Framework中还有对 IIdentity IPrincipal接口的其它实现;

System.Web.Security.FormsIdentity类:用于 ASP.NET中的表单验证。

System.Web.Security.PassportIdentity类:用于 Passport安全机制。

 

Windows 中使用安全标识符 SID )标识用户与用户组。类似与 GUID 在一定时间与空间内 SID 是唯一的。

.NET Framework 提供了三个类,其实例代表一个 SID

System.Security.Principal.IdentityReference

    System.Security.Principal.NTAccount

    System.Security.Principal.SecurityIdentifier

其中 NTAccout 类以一种人类可读的方式表示一个 SID ,而 SecurityIdentifier 则用于将一个 SID 传给 Windows

这些类的应用先看如下示例:该示例方法用于验证当前的 SID 是否属于某个特定的 Windows 组。

using System.Security.Principal;

class Program {

static void Main() {

WindowsIdentity id = WindowsIdentity .GetCurrent();

SecurityIdentifier sid = id.User;

NTAccount ntacc = sid.Translate( typeof ( NTAccount ) ) as NTAccount ;

System.Console .WriteLine( "SID: " + sid.Value);

System.Console .WriteLine( "NTAccount: " + ntacc.Value);

if ( sid.IsWellKnown( WellKnownSidType .AccountAdministratorSid ) )

System.Console .WriteLine(" 这个管理员账户 " );

}

}

代码说明:

WindowsIdentity User 属性表示当前 Windows 用户的 SID

System.Security.Principal.WellKnownSidType 枚举值代表了大部分系统默认提供的用户组。

SecurityIdentifier 的实例方法 ISWellKnown(WellKnownSidType) 用于验证当前 SID 是否属于某个特定的 Windows 组。

GetCurrent() 函数的一个重载版本可以接受一个 bool 参数。当此参数为 true 时,仅当线程模拟另一个用户(模拟用户概念见下文)时,该方法才返回用户的身份,当该布尔量为 false 时,则只有在线程没有模拟其他用户时,该方法才返回用户的身份。

 

在线程中模拟用户

默认情况下,一个 Windows 线程运行于它所在的进程上下文中。我们可以使用 Win32 函数 WindowsIdentity.Impersonate() 为一个线程的上下文关联一个用户,称作线程在模拟用户。过程是使用 LogonUser() 方法以一个用户登录,获取一个 Identity ,并将该 Identity 传入 Impersonate() 方法,开始模拟用户。

示例代码如下:

using System.Runtime.InteropServices;

using System.Security.Principal;

class Program{

[DllImport ("Advapi32.Dll" )]

static extern bool LogonUser(

string sUserName,

string sDomain,

string sUserPassword,

uint dwLogonType,

uint dwLogonProvider,

out System.IntPtr token);

[DllImport ("Kernel32.Dll" )]

static extern void CloseHandle( System.IntPtr token );

 

static void Main(){

WindowsIdentity id1 = WindowsIdentity .GetCurrent();

System.Console .WriteLine( " 模拟用户前: " + id1.Name);

System.IntPtr pToken;

if ( LogonUser(

"guest" , // login

string .Empty, // Windows domain

"guest_pwd" , // password

2, // LOGON32_LOGON_INTERACTIVE

0, // LOGON32_PROVIDER_DEFAUT

out pToken) ) {

WindowsIdentity .Impersonate( pToken );

WindowsIdentity id2 = WindowsIdentity .GetCurrent();

System.Console .WriteLine( " 模拟用户后: " + id2.Name );

// Here, the underlying Windows thread ...

// ... has the 'guest' identity.

CloseHandle( pToken );

}

}

}

 

资源访问控制

Windows 访问控制介绍

一个 Windows 资源(一个文件,一个同步对象或一个注册表项)都在物理上包含了一些与之对应的访问权限的信息, Windows 可以根据这些信息推断出哪些用户对该资源拥有何种访问权限。这些信息包含在一个与资源关联的结构体 - 安全描述符 SD )中。针对某些类型的资源, Windows 允许 SD 有继承性。这个功能使执行一次操作就可以设置有多个子目录的权限。

一个 SD 含有一个代表资源创建者或拥有者的 SID 及一个自由访问控制列表 Discretionary Access Control List, DACL )。 DACL 是一个有序的访问控制项( Access Control Elements, ACE )列表,而 ACE 是一个将 SID 与一个将 SID 与一个访问权限列表关联的结构。 DACL 中的 ACE 有以下两种类型:

  • 授予相应 SID 访问权限的 ACE
  • 拒绝授予其 SID 访问权限的 ACE

当一个线程试图获得一个资源的某些访问权限时, Windows 将根据线程的 SID 以及资源的 DACL 作出相应的判断。在判断过程中, ACE 的访问顺序将由它们在 DACL 中的存放顺序决定。每个 ACE 会根据它所包含的 SID 允许或拒绝该线程对资源的访问。一旦所有请求的访问权限都通过检查,这些权限就会立即被授予线程。而一旦其中某个访问权限被拒绝,那么所有请求的访问权限都会被拒绝。而且 ACE DACL 中的存放顺序是有关的,因此在请求访问权限的时候 Windows 并不需要判断所有的 ACE

一个 Windows 资源的每个 SD 还包含另一个称作系统访问控制列表 System Access Control List, SACL )的 ACE 列表, Windows 使用该列表审核对某个资源的访问。与 DACL 中的 ACE 一样, SACL 中的 ACE 也是将一列访问权限与每个 SID 关联。其不同之处在于 SACL 中的 ACE 包含了两份二进制信息,其作用如下:

  • 如果该 ACE 关联的 SID 允许获取某项访问权,那么授予该权限这个事件发生时是否要被记录。
  • 如果该 ACE 关联的 SID 被拒绝授予某项访问权限,那么拒绝该权限这个事件发生时是否要被记录。

由上可知, SACL ACE 的存放顺序是无关紧要的。

 

System.Security.AccessControl 中定义了一些类型来使用 SD ,下面分两部分讨论:

.NET 代码中使用特殊的 SD

特殊的 SD 指可以专门用于操作 Windows 中特殊资源的 SD 的类,这个 SD 类型体系包括三部分:

  1. 表示 SD (包括 DACL SACL )的类型体系

    此类型体系中的类有:

    System.Security.AccessControl.ObjectSecurity

        System.Security.AccessControl.DirectoryObjectSecurity

            System.DirectoryServices.ActiveDirectorySecuirity

        System.Security.AccessControl.CommonObjectSecurity

Microsoft.Iis.Metabase.MetaKeySecurity

System.Security.AccessControl.NativeObjectSecurity

    System.Security.AccessControl.EventWaitHandleSecurity

    System.Security.AccessControl.FileSystemSecurity

    System.Security.AccessControl.DirectorySecurity

    System.Security.AccessControl.FileSecurity

    System.Security.AccessControl.MutexSecurity

    System.Security.AccessControl.RegistrySecurity

    System.Security.AccessControl.SemaphoreSecurity

以上这些类接受代表 ACE 类型的参数以填充其中的 DACL SACL 。注意 ACE 有代表 DACL SACL 之分(下文介绍)。

  1. 表示 ACE 的类型

    这些类型的对象用于填充 SD DACL SACL ,所以这些代表 ACE 的类被分为表示 DACL 的访问规则,以及代表用于 SACL 审核的 ACE 的类。

        System.Security.AccessControl.AuthorizationRule

        System.Security.AccessControl.AccessRule

            Microsoft.Iis.Metabase.MetaKeyAccessRule

    System.Security.AccessControl.EventWaitHandleAccessRule

    System.Security.AccessControl.FileSystemAccessRule

    System.Security.AccessControl.MutexAccessRule

    System.Security.AccessControl.ObjectAccessRule

        System.DirectoryServices.ActiveDirectoryAccessRule

            System.DirectoryServices. [*]AccessRule

    System.Security.AccessControl.RegistryAccessRule

    System.Security.AccessControl.SemaphoreAccessRule

System.Security.AccessControl.AuditRule

        Microsoft.Iis.Metabase.MetaKeyAuditRule

System.Security.AccessControl.EventWaitHandleAuditRule System.Security.AccessControl.FileSystemAuditRule System.Security.AccessControl.MutexAuditRule System.Security.AccessControl.ObjectAuditRule

    System.DirectoryServices.ActiveDirectoryAuditRule

System.Security.AccessControl.RegistryAuditRule

System.Security.AccessControl.SemaphoreAuditRule

  1. 代表各种访问权限的枚举值列表。

    Microsoft.Iis.Metabase.MetaKeyRights

    System.Security.AccessControl.EventWaitHandleRights

    System.Security.AccessControl.FileSystemRights

    System.Security.AccessControl.MutexRights

    System.Security.AccessControl.ObjectRights

    System.Security.AccessControl.SemaphoreRights

    如: FileSystemRights 枚举值包含 AppendData 等,而 MutexRights 枚举量含有值 TakeOwnership

另外,正如下面示例中将看到的, .NET Framework 中直接代表相关 Windows 资源的各种不同类型(如 System.Threading.Mutex System.IO.File …)都有一个接受 ACL 的构造函数以及一个用于设置和获取该类型实例的 ACL 的方法 Set/GetAccessControl()

示例:使用 DACL 创建一份文件。

using System.Security.AccessControl;

using System.Security.Principal;

using System.IO;

class Program {

static void Main() {

// 创建 DACL

FileSecurity dacl = new FileSecurity ();

// 创建 ACE

FileSystemAccessRule ace = new FileSystemAccessRule (

WindowsIdentity .GetCurrent().Name,

FileSystemRights .AppendData | FileSystemRights .ReadData,

AccessControlType .Allow);

// 使用 ACE 填充 DACL

dacl.AddAccessRule( ace );

// 使用 DACL 创建一个文件

System.IO.FileStream fileStream = new System.IO.FileStream (

@"file.bin" , FileMode .Create , FileSystemRights .Write ,

FileShare .None, 4096 , FileOptions .None, dacl );

fileStream.Write( new byte [] { 0, 1, 2, 3 }, 0, 4 );

fileStream.Close();

}

}

经上述操作后可以在文件属性页的安全选项卡产看文件的访问权限。

 

.NET 代码中使用通用的 SD

这些方式是操作 SD 类型的通用方式(与底层的 Windows 资源的类型无关), .NET 提供了如下类型:

  1. 表示 SD

    System.Security.AccessControl.GenericSecurityDescriptor

    System.Security.AccessControl.CommonSecurityDescriptor

    System.Security.AccessControl.RawSecurityDescriptor

  2. 表示 ACL DACL SACL

    System.Security.AccessControl.GenericAcl

        System.Security.AccessControl.CommonAcl

            System.Security.AccessControl.DescretionaryAcl

            System.Security.AccessControl.SystemAcl

        System.Security.AccessControl.RawAcl

  1. 表示一个 ACE

    System.Security.AccessControl.GenericAce

        System.Security.AccessControl.CustomAce

        System.Security.AccessControl.KnownAce

            System.Security.AccessControl.CompoundAce

            System.Security.AccessControl.QualifiedAce

                System.Security.AccessControl.CommonAce

                System.Security.AccessControl.ObjectAce

 

示例:创建一个 SD ,向其 DACL 中添加 ACE 并将其转化为一个代表 Windows 资源的特殊 SD (此处为互斥体)。

 

using System;

using System.Security.AccessControl;

using System.Security.Principal;

class Program {

static void Main() {

// 创建一个新的 SD

CommonSecurityDescriptor csd = new CommonSecurityDescriptor (false , false , string .Empty);

DiscretionaryAcl dacl = csd.DiscretionaryAcl;

// DACL 中添加 ACE

dacl.AddAccess(

AccessControlType .Allow, // 枚举 Allow Deny.

WindowsIdentity .GetCurrent().Owner,

(int )MutexRights .TakeOwnership | (int )MutexRights .Synchronize,

InheritanceFlags .None, // 禁用 ACE 继承

PropagationFlags .None);

 

string sSDDL = csd.GetSddlForm( AccessControlSections .Owner );

 

MutexSecurity mutexSec = new MutexSecurity ();

mutexSec.SetSecurityDescriptorSddlForm( sSDDL );

AuthorizationRuleCollection aces = mutexSec.GetAccessRules(true , true , typeof (NTAccount ));

foreach ( AuthorizationRule ace in aces ) {

if (ace is MutexAccessRule ) {

MutexAccessRule mutexAce = (MutexAccessRule )ace;

Console .WriteLine( "-->SID : " + mutexAce.IdentityReference.Value );

Console .WriteLine( " 访问权限类型: " + mutexAce.AccessControlType.ToString());

if (0xffffffff == (uint ) mutexAce.MutexRights)

Console .WriteLine( " 拥有所有权限 " );

else

Console .WriteLine( " 权限 : " + mutexAce.MutexRights.ToString());

}

}

}

}

另: CommonAcl.Purge(SecurityIdentifier) 可以在 ACL 中删除某个 SID 所拥有的 ACE 项。

 

.NET 与角色

     类似于一个 Windows 线程在一个 Windows 安全上下文中执行,一个托管线程也可以根据开发者的选择在一个安全上下文中执行,这样既可以在 Windows 中利用用户 / 角色安全机制,还可以在诸如 ASP.NET 这样的环境中使用。实现这个功能的关键是 Thread 类提供的 IPrincipal CurrentPrincipal{get;set; } 属性。

一个主体可以通过 3 种不同的方式与一个线程关联。

  • 使用 Thread.CurrentPrincipal 属性显示地将一个主体与一个托管线程关联。
  • 为应用程序域定义一个主体策略 。当托管线程执行到该域中的代码时,域中的主体策略最终会把主体与线程关联起来(除非该线程已经被显示地关联上了一个主体)。
  • 可以为某个应用程序域中创建的或渗入进来的所有线程赋予一个特定的主体,前提是这些线程没有主体与之关联。为了实现这个目标,只需使用 void AppDomain.SetThreadPrincipal(IPrincipal) 方法。

以上这三个操作需要执行 SecurityPermissionFlag.ControlPrincipal 元权限。

 

定义应用程序域的主体策略

下面示例将当前应用程序域的主体策略设置为 WindowsPrincipal 。(这意味着当一个托管线程执行该域中的代码时,如果尚未有一个主体显示地与它关联,那么底层的 Windows 安全上下文将自动与其关联。)

using System;

using System.Security.Principal;

class Program{

static void Main(){

AppDomain .CurrentDomain.SetPrincipalPolicy(

PrincipalPolicy .WindowsPrincipal);

IPrincipal pr = System.Threading.Thread .CurrentPrincipal;

IIdentity id = pr.Identity;

Console .WriteLine( "Name : " + id.Name );

Console .WriteLine( "Athenticated? : " + id.IsAuthenticated );

Console .WriteLine( "Authentification type : " +id.AuthenticationType);

}

}

程序输出如下结果:

Name : Dev/Administrator

Athenticated? : True

Authentification type : NTLM

 

在一个应用程序域中可能定义的其他主体策略有:

PrincipalPolicy.NoPrincipal ,表示没有主体与线程关联。(此种情况下, Thread.Current.Principal 默认为 null )。

PrincipalPolicy.Unauthenticated 表示一个未通过身份认证的主体与线程关联。(这时, CLR Thread.CurrentPrincipal 属性与一个未通过身份验证的 GenericPrincipal 实例关联。)

后者是所有应用程序域默认采取的主体策略。

 

检查用户是否属于某个特定角色

有三种途径验证与托管线程关联的主体的角色:

  1. 使用 IPrincipal 接口的 IsInRole() 方法。

    代码示例如下:

using System.Security.Principal;

class Program{

         static void Main(){

     IPrincipal pr = System.Threading.Thread .CurrentPrincipal;

     if ( pr.IsInRole( @"BUILTIN/Administrators" ) ){

     // 此处主体为管理员身份

         }

     else

System.Console .WriteLine(

         "You must be an administrator to run this program!" );

     }

}

  1. 使用上文示例中示例的 WellKonwnSidType 方法,该方法也是验证一个 Windows 用户是否是一个 Windows 组的成员。但主要用于 Windows 用户与角色这种特殊情况,因为此方法可以更好的适应不同语言的操作系统。
  2. 使用前文详细总结过的 System.Security.Permissions 命名空间下的类。

    1).     使用该命名空间下的 PrincipalPermissions 类,示例如下:

        static void Main(){

            try {

        PrincipalPermission prPerm = new PrincipalPermission (

                 null , @"BUILTIN/Administrators" );

        prPerm.Demand();

        // 此处主体为管理员主体

            }

            catch (System.Security.SecurityException ){

            System.Console .WriteLine(

        "You must be an administrator to run this program!" );

            }

}

这种方法的优点在于,其为角色管理与权限管理提供了一种较为一致的方式;另外它允许在一个操作中验证多个角色。示例:

         ……

        PrincipalPermission prPermAdmin = new PrincipalPermission (

null , @"BUILTIN/Administrators" );

PrincipalPermission prPermUser = new PrincipalPermission (

null , @"BUILTIN/Users" );

System.Security.IPermission prPerm =prPermAdmin.Union(prPermUser);

prPerm.Demand();

         ……

2).     有上篇文章可知道,上述类有一个对应的 Attributte - PrincipalPermissionAtrribute 。该类的使用如下:

        [PrincipalPermission ( SecurityAction .Demand,

Role= @"BUILTIN/Administrators" )]

static void Main() {

// 此处是管理员主体

}

同样,如果想允许多个角色访问该方法,可以多次使用 attribute 当用户是其中至少一个角色的成员时,他就被允许访问该方法。这种声明角色权限的 attribute 也可以被应用在类上,这时只有相关角色的人可以创建此类型的实例,这也是不能在构造函数上使用角色 attribute 的一种替代方式。

另外多说一点,角色 attribute 不仅可以声明角色的权限,它还可以赋予单个用户的权限。如下面这样:

[PrincipalPermission ( SecurityAction .Demand, Name= @"MStar" )]

或同时约束两者:

[PrincipalPermission ( SecurityAction .Demand, Name= @"MStar" ,

Role= @"BUILTIN/Administrators" )]

但这种写法不建议,因为硬编码用户名的程序是脆弱的。

Windows 环境下使用这样的写法就足够了,因为当前用户已经通过了身份验证(登录 Windows 的过程),但是在 Web 环境中,需要强制验证后再判断用户角色,所以应该如下形式的 attribute:

[PrincipalPermission ( SecurityAction .Demand, Authenticated = true, Role= @"BUILTIN/Administrators" )]

 

经过两篇文章把代码访问权限与角色访问权限总结的差不多了,最后做为补充总结一下 .NET 中的安全性工具与安全方面的异常管理。

安全性工具

此处总结了权限与程序集管理工具,证书相关工具放到专门介绍证书的部分。

.NET Framework SDK 提供的许可和程序集管理工具

程序名称

作用

Caspol.exe

表示代码访问安全策略工具。允许查看和修改安全设置

Signcode.exe

文件标记工具,允许对可执行文件进行数字标记

Storeadm.exe

用于独立存储区管理的工具,限制对文件系统的代码访问

Permview.exe

显示程序集请求的访问权限

Peverify.exe

检查可执行文件是否对类型安全编码进行了运行时检测

Secutil.exe

从证书中提取公共密钥,并以能重用的格式放在源代码中

Sn.exe

用强名创建程序集,即给命名空间和版本信息添加数字标记

 

使用 SecurityException 类处理异常

.NET Framework 中的 SystemException 2.0 版本进行了很大了的扩展,可以提供更详细、安全环境中所遇到的各种异常信息。

下面表格总结了 SystemException 类的新属性内容

属性

描述

Action

获取导致异常发生的安全行为

Demanded

返回导致错误发生的权限、权限组或者权限组集合

DenySetInstance

返回导致安全操作失败的被拒绝权限、权限组或者权限组集合

FailedAssemblyInfo

返回失败的程序集的信息

FirstPermissionThatFailed

返回失败的权限组或者权限组集合中的第一个权限

GrantedSet

返回导致安全行为失败的一组权限

Method

返回连接到异常的方法的信息

PermissionState

返回抛出异常的权限状态

PermissionType

返回抛出异常的权限类型

PermitOnlySetInstance

如果安全行为失败,就返回 permit-only 堆栈结构中的一个权限组或者权限集合

RefusedSet

返回被程序集拒绝的权限

Url

返回导致异常的程序集的 URL

Zone

返回导致异常的程序集的环境

 

 

参考书籍: C# .NET2.0 实战

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值