.Net安全编程实战(二)

来源:互联网 发布:网络音频播放器怎么用 编辑:程序博客网 时间:2024/06/03 17:13

前面已经提到了3.Net的代码访问安全机制,下面讲讲基于角色的安全机制。基于角色的访问机制已经不是一件新鲜事情了,用户登陆一个系统,提供他自己的身份,提供身份的方式很多,比如最常用的用户名密码,指纹验证,证明身份的卡验证等等。当用户证明了他自己的身份,系统就能分配给用户相应的资源,比如访问的磁盘空间,数据库等等。在.Net中,被调用的代码(资源)能够根据用户所提供的用户身份确定用户是否有足够权限来执行这段代码。这个过程可以如下图所示:

   当调用者为了调用某个程序集的某个方法,该方法会要求CLR检查调用者的身份,调用者身份如果正确则该程序集被调用,否则,该程序集拒绝调用并抛出异常。这里您可能有疑问了,平时你的代码也没涉及什么身份验证啊,首先您要知道您的被调用程序集中的方法没有要求提供这个身份,再者你的调用者的代码是在缺省的角色下运行着的,只是你不知道而已,但是在某些情况下你的代码就会有问题了,笔者曾今在Sql Server 2005下用CLR存储过程访问网络资源,结果出现AccessDenied的异常,因为当前的角色是Sql server 2005的帐户,没有足够的权限访问网络资源。

接下来我们看看.Net的基于角色的机制具体是什么样的,.Net的角色对应着一个接口IPrincipal,如图:

   该接口代表了当前.Net程序的角色,里面有个IIdentity接口,这个接口包含了角色的名称,验证类型等,具体的角色对象当然还包含更多与该角色相关的信息,不过这两个接口已经可以提供一个帐户的身份信息了。您可能会想到,这里的IPrincipalWindows所提供的角色有关系吗?当然,事实上,Windows里面所有的帐户,都可以创建出实现了IPrincipal的对象,他们可以很容易的被创建出来给作为身份验证。类WindowsIdentity完成了这件事情,你可以使用WindowsIdentity identity = WindowsIdentity.GetCurrent();得到当前的Windows帐户作为当前应用程序的角色。然后你可以通过WindowsPrincipal的构造函数获得WindowsPrincipal。这里有个问题要注意,就是这个WindowsIdentityWindows帐户本身是不同的,windows帐户可以访问的资源是Windows系统所赋予的,而这里的角色信息是CLR用来验证调用者身份的,他们根本不是一回事,没有必然关联。

接下来我们看看怎么用这个机制,首先我们必然要为我们当前的应用程序设置一个角色,举如下这个例子,我们将当前的运行程序集的身份设置成一个Windows内置帐户:

 

using System;using System.Collections.Generic;using System.Text;using System.Threading;using System.Security.Principal;using System.Reflection;using System.Runtime.InteropServices;using System.IO;namespace Hello{    public class Program    {        public static void Main(string[] args)        {            WindowsIdentity identity = WindowsIdentity.GetCurrent();            WindowsPrincipal principal = new WindowsPrincipal(identity);            AppDomain.CurrentDomain.SetThreadPrincipal(principal);            //Thread.CurrentPrincipal = principal;        }    }}

  这里可以看到两种方式都可以设置当前用户的身份,AppDomain.CurrentDomain.SetThreadPrincipalThread.CurrentPrincipal都可以接受一个实现IPrincipal的对象,设置当前运行代码的角色。

 

现在我们把这个角色用起来,创建一个简单的新的lib工程,创建一个新库RBLib,并且增加一个类RoleBasedClass,如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Security.Permissions; namespace RBLib{[PrincipalPermission(SecurityAction.Demand, Role="BuiltIn//Administrator", Name = "WindowsAccountName")]public class RoleBasedClass    {        public void Print()        {            Console.WriteLine("Hello");        }    }}

 

注意那个Attribute,它的Name就是需要验证的身份的名字,这个名字检查的是你的IIdentityName,对应的还有角色名称,对于Windows帐户,Role就是类似于BuiltIn/Administrator这样的组名,你在创建WindowsIdentity的时候就会创建。你可以用IPrincipal.IsInRole来判定你当前的角色是不是符合条件。一个用户帐户是可以拥有多个角色的,只要有一个符合当前被调用程序集的要求就可以了。这里我们检查了NameRole都必须符合才行。

当然你不必要一定要使用Windows的内置帐户,有一个类可以帮我们,请看如下代码:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Security.Principal;using System.Reflection;using System.Runtime.InteropServices;using System.IO; namespace Hello{    public class Program    {                  public static void Main(string[] args)        {            // Create a GenericIdentity for the user Peter            GenericIdentity gi = new GenericIdentity("GenericId");             // Create a GenericPrincipal for Peter and specify membership of the            // Developers and Managers roles            String[] roles = new String[] { "Developers", "Managers" };            GenericPrincipal gp = new GenericPrincipal(gi, roles);             // Assign the new principal to the current thread            Thread.CurrentPrincipal = gp;              RBLib.RoleBasedClass cls = new RBLib.RoleBasedClass();            cls.Print();         }    }}

这个GenericIdentity就是我们定义的一个身份,名字是GenericId, RoleDeveloperManager

个人觉得这个类引入得真不好,我只要知道了被调用的程序集用了什么PrincipalPermisiion就可以造出对应的Identity,如果没有该Permission异常可以完全的告诉你这个信息。呵呵,或者是我理解不够吧。

最后说说Impersonate。这个概念很重要,也很有用。在Windows帐户这个前提下,当有必要在程序运行过程中更换角色时,更换前的角色所拥有的Token是不变的,这个TokenWindows帐户访问某些资源所必须的(你平时登陆Windows以后就获得了这个Token,确保你访问一些资源,比如登陆域里面的服务器,它是你Windows帐户的身份标识)。有人可能有疑问,为什么需要Impersonate,你如果做过Asp.Net,需要在服务器上访问某些需要Administrator权限才能访问的资源,你就会明白这个东西的重要性,因为Asp.net缺省并不是用的Windows帐户,而是用的Asp.Net自己的帐户,它的权限是很小的,不能通过它访问一些它不具备权限的资源,因此在访问这些资源的时候必须更换角色,如下面的代码:

using System;using System.IO;using System.Security.Principal;using System.Security.Permissions;using System.Runtime.InteropServices; // Make sure we have permission to execute unmanged code[assembly:SecurityPermission(SecurityAction.RequestMinimum,    UnmanagedCode=true)] public class WindowsImpersonationTest {                // Define the external LogonUser method from advapi32.dll.    [DllImport("advapi32.dll", SetLastError=true)]    static extern int LogonUser(String UserName, String Domain,         String Password, int LogonType, int LogonProvider,        ref IntPtr Token);     public static void Main(  ) {         // Create a new initialized IntPtr to hold the access token        // of the user to impersonate.        IntPtr token = IntPtr.Zero;                // Call LogonUser to obtain an access token for the user        // "Bob" with the password "treasure". We authenticate against        // the local accounts database by specifying a "." as the Domain        // argument.        int ret = LogonUser(@"Bob", ".", "treasure", 2, 0, ref token);                // If the LogonUser return code is zero an error has occured.         // Display it and exit.        if (ret == 0)  {                   Console.WriteLine("Error {0} occured in LogonUser",                 Marshal.GetLastWin32Error(  ));            return;        }               // Create a new WindowsIdentity from Bob's access token        WindowsIdentity wi = new WindowsIdentity(token);            // Impersonate Bob, saving a reference to the returned         // WindowsImpersonationContext.        WindowsImpersonationContext impctx = wi.Impersonate(  );                // !!! Perform actions as Bob !!!        // We create a file that Windows will show is owned by Bob.        StreamWriter file = new StreamWriter("test.txt");        file.WriteLine("Bob's test file.");        file.Close(  );                // Revert back to the original Windows user using the         // WindowsImpersonationContext object.        impctx.Undo(  );        }} 

.Net 没有提供直接创建一个WindowsIdentityToken的方法,因此这里调用了WindowsAPI来创建一个WindowsIdentity,如果你使用当前登陆的Windows帐户的Identity可以直接用WindowsIdentity.GetCurrent 注意当访问必要的资源完成以后马上将角色再替换回来。如果还有不明白的地方,请参阅MSDNImpersonate是非常重要的概念,在很多情况下都需要它(如Asp.net, Web service, Sql server CLR),但是值得一提的是.Net如果调用了COM组件,COM组件是不会根据.Net的角色更换它的角色的,这点很容易出错。恕笔者经验有限,谁知道如何更换COM组件的运行角色,请告诉一声:)

后面我们接着讨论.Net安全的隔离存储机制,(先把简单点的讨论完,再讨论那个头大的CAS,呵呵)