• 在Silverlight+WCF中应用以角色为基础的安全模式(一)基础篇之角色为基础的安全模式简介 Virus


      

        引言

      最近一直在研究Silverlight+WCF应用中的安全问题,如何设计一个安全,又符合Silverlight和WCF的规范的应用呢?又可以将以前的角色为基础的开发框架拿来主义呢?

      我们知道WCF在安全方面提供了很多的绑定协议,可是Silverlight3+WCF的话,只有basicHttpBinding可以使用,这就使得我们的选择不多,还有就是项目本身是一个互联网应用,还是使用比较通用的角色为基础的权限系统比较好。

      这个系列有两篇文章,一篇讲解.NET框架提供我们的角色为基础的安全模式,以及如何根据我们的需求,自定义角色为基础的安全模式;一篇讲解在Silverlight+WCF应用中,如何设计的一种角色为基础的应用方法。

      文中的代码下载:/Files/virusswb/RetrieveSecurity_src.zip

      正文

      

      .NET中的角色为基础的安全

      .NET 框架使得 你在应用中实现以角色为基础的安全模式非常容易。迫使安全有两部分组成,认证和授权。认证就是验证你的身份。应用程序验证你就是你所声明的人。通常的做法是用户输入用户名和密码,应用查找你输入的用户名,然后验证你输入的密码是否匹配。更高级的做法是依赖生物认证,例如:指纹或者是视网膜,又或者是一张绑定了个人PIN码的认证卡。如果认证失败,用户将不被允许进入系统,除非系统允许匿名访问,意味着如果系统确认了你的身份,就授予你访问权。授权就是确认用户是否能操作系统的某项功能。授权依赖于已知的用户身份以及和用户相关的安全信息,基于这些安全信息,系统就可以批准或者拒绝用户的请求。

      .NET框架提供了通过Identity访问用户信息,通过principal访问授权信息。Thread.CurrentPrincipal提供了当前线程的principal信息,默认情况下,它是一个非认证的授权。框架提供了两种不同的principal,一个是windows principal,一个是通用的授权generic principalWindows principal工作在windows 操作系统上。所以,当前运行的线程会映射到一个windows帐户上。如果你正在运行一个windows form的应用程序,它就是一个用户。

    有两个办法可以访问windows principal

    // set that a principal should be attached to the thread and

    // it should be a windows principal

    AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    // get hold of the windows principal

    WindowsPrincipal MyPrincipal = (WindowsPrincipal)Thread.CurrentPrincipal;

    // get the windows identity

    WindowsIdentity MyIdentity = MyPrincipal.Identity;

     

      通过调用当前程序域的SetPrincipalPolicy,你告诉框架当前线程需要附加的principal,你需要在第一次访问principal之前做这些设置。调用Thread.CurrentPrincipal返回和当前线程绑定的principal。第一次这么做的时候,框架会查询windows的帐户信息创建一个windows身份和一个windows授权并且绑定到这个线程。从windows principal你可以访问windows identity。另一个办法是

    // get an identity object for the windows user

    WindowsIdentity Identity = WindowsIdentity.GetCurrent();

    // get hold of the windows principal

    WindowsPrincipal MyPrincipal = new WindowsPrincipal(Identity);

      WindowsIdentity.GetCurrent()查询windows帐户信息,同时创建一个identity代表当前用户,那样你可以用这个identity创建一个principal。这    样做的缺点就是每次都需要查询windows帐户,然后创建一个identity和一个principal。第一种方法每次都会使用相同的identityprincipal。通用的principal允许你创建不绑定任何windows帐户的一个principalidentity

    // create the generic identity GenericIdentity

    Identity = new GenericIdentity("Administrator");

    // define the roles to associate with the generic principal

    string[] Roles = new string[2] { "Manager", "Architect" };

    // create the generic principal

    GenericPrincipal MyPrincipal = new GenericPrincipal(Identity,Roles);

    // bind the generic principal to the thread

    Thread.CurrentPrincipal = MyPrincipal;

      首先创建一个通用的identity,你需要提供identity的名称,因为他不绑定任何windows帐户,需要一个用户名。然后定义你想要这个授权拥有的角色,最后创建一个principal,然后提供identity和角色列表。然后你可以将这个授权绑定到当前线程。

    创建自定义的授权principal和认证identity

      .NET框架允许通过实现IPrincipalIIdentity接口,来自定义授权和认证。本文下面的代码,将展示如何创建一个数据库驱动的认证和授权。

      

      授权过程在用户表中检查提供的用户名和密码。授权成功之后,读取用户信息和用户的安全组信息,查看用户属于那些安全组。

      这些信息对于自定义认证和授权都是必要的。但是授权还可以更进一步,还可以检查个人权限信息,例如:用户是否被允许查看预算等。这些信息都是从SecurityRightAssign表中读取出来,让我们先创建一个自定义身份。

    public class UserIdentity : IIdentity

    {

    // the authentication type for us is always database

    private static string AuthenticationTypeString = "Database";

    // hash table with all the user info we have

    private Hashtable UserInfo;

    // create the user identity; all user information is in the hashtable passed along

    private UserIdentity(Hashtable UserInfo)

    {

          this.UserInfo = UserInfo;

    }

    //create a user identity and return it to the caller

    public static UserIdentity CreateUserIdentity(Hashtable UserInfo)

    {

          return new UserIdentity(UserInfo);

    }

    }

     

      UserIdentity实现了IIdentity接口,需要我们事先三个属性。类型的构造函数被私有化,防止通过实例化来构造对象。你需要通过静态方法CreateUserIdentity,传递一个HashTable结构的用户类型,然后创建一个身份的实例。Name属性返回这个身份的名称。

    // returns the name of the identity

    public string Name

    {

      get

      {

        return

        Convert.ToString(UserInfo[UserNameKey],CultureInfo.InvariantCulture).Trim();

      }

    }

    // returns if identity is authenticated or not

    public bool IsAuthenticated

    {

      get

      {

        return true;

      }

    }

    // the type of authentication

    public string AuthenticationType

    {

      get

      {

        return AuthenticationTypeString;

      }

    }

      IsAuthenticated属性返回用户是否被认证通过,在上面的代码中用户总是被认证功过,因为我们return true。如果你允许匿名访问,你就可以为匿名用户设置为false。最后一个属性AuthenticationType返回的是验证的类型,在我们的代码中返回的是“Database”。WindowsIdentity返回的是NTLMGenericIdentity返回的是空字符串或者是实例化GenericIdentity的时候传递的验证类型。下面,我们里实现自定义的principal

    public class SecurityPrincipal : IPrincipal

    {

    // stores the list of security rights the user belongs too

    private Hashtable SecurityGroups;

    // stores the list of security rights the user has

    private Hashtable SecurityRights;

    // the user identity we create and associate with this principal

    private UserIdentity TheUserIdentity;

    // constructor: stores role and permission info and creates custom identity

    private SecurityPrincipal(Hashtable SecurityGroups, Hashtable SecurityRights,

                      Hashtable UserInfo)

    {

          this.SecurityGroups = SecurityGroups;

          this.SecurityRights = SecurityRights;

          // creates the IIdentity for the user and associates it with this IPrincipal

          TheUserIdentity = UserIdentity.CreateUserIdentity(UserInfo);

    }

    // create the security principal and return it to the caller

    public static SecurityPrincipal CreateSecurityPrincipal(Hashtable SecurityGroups,

                      Hashtable SecurityRights, Hashtable UserInfo)

    {

          return new SecurityPrincipal(SecurityGroups,SecurityRights,UserInfo);

    }

    }

      实现IPrincipal接口需要实现Identity属性和IsInRole()方法,同样的这个类型的构造函数也是私有的,防止通过实例化来创建对象。你需要调用静态方法CreateSecurityPrincipal,传递一个hashtable类型的用户信息,一个用户所属的角色信息,还有就是用户在系统中的特权。这个类型的构造函数调用自定义的Identity方法的静态函数CreateUserIdentity,将用户信息传递给CreateUserIdentity方法,然后返回一个UserIdentityCreateSecurityPrincipal方法返回一个自定义的principal实例。Identity属性返回和这个principal相关联的identity

    // returns the Identity object associated with the principal

    public IIdentity Identity

    {

          get

          {

                return TheUserIdentity;

          }

    }

    // checks if user belongs to role

    public bool IsInRole(string Role)

    {

          return SecurityGroups.ContainsValue(Role);

    }

    // checks if user has permission

    public bool HasPermission(string Permission)

    {

          return SecurityRights.ContainsValue(Permission);

    }

      IsInRole方法检查用户是否属于角色,是通过检查角色是否在hashtable类型的SecurityGroups中,然后返回true 或者false。我们自定义的principal还实现了一个方法HasPermission,它和IsInRole方法类似,但是检查的是提供的权限是否在特权列表中,然后返回true或者false

    这些已经实现了自定义的identityprincipal,下面的代码解释了信息是如何从数据库中读取,最后要做的就是去使用它。

    public static IPrincipal SetSecurityPrincipal(Hashtable SecurityGroups,

                                   Hashtable SecurityRights, Hashtable UserInfo)

    {

    // set that we want to use authentication within the current app-domain;

    // this means a thread will have a IPrincipal associated which is then

    // used by the .NET security classes when checking role based security

    AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

    // we switch to the new security principal only if we didn't do so already;

    // protects us from the client calling the method multiple times

    if (!(Thread.CurrentPrincipal is SecurityPrincipal))

    {

      // create a new instance of the security principal which we can do only

      // within a class member as we marked the constructor private

      SecurityPrincipal TheSecurityPrincipal = new SecurityPrincipal(SecurityGroups,

                      SecurityRights,UserInfo);

      // get a reference to the current security principal so the caller

      //can keep hold of it

      IPrincipal CurrentSecurityPrincipal = Thread.CurrentPrincipal;

      // set the security principal for the executing thread to the newly created one

      Thread.CurrentPrincipal = TheSecurityPrincipal;

      // return the current security principal;

      return CurrentSecurityPrincipal;

    }

    // return null if we don't switch the security principal

    else

          return null;

    }

    为了使用,我们在SecurityPrincipal类型上提供了一个静态方法SetSecurityPrincipal。首先调用AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);

      这样做看起来是错误的,因为我们想要的不是一个windows principal,而是一个自定义的principal。这么做只是为了保证我们拥有一个绑定到当前线程的principal,然后我们检查绑定到当前线程的principal是否是自定义的principal类型。如果是的话,我们什么都不需要做,因为我们已经为当前线程分配了我们自定义的principal,这确保了调用者在多线程的环境中调用不会产生负面的问题。只是在第一次我们会发现没有绑定到自定义的principal,这时候我们创建自定义的principal,创建自定义的identity,并且绑定到当前线程。如果调用者需要的话,我们返回当前principal给他。

      在使用Thread.CurrentPrincipal访问用户信息的时候,会检查用户的角色和特权,这些都可以通过在principal上调用IsInRole或者是访问identity来实现。如果你想检查用户的特权,你可以使用从Thread.CurrentPrincipal中获取的principalHasPermission方法来实现。

    public bool CheckSecurityPermission(string Permission)

    {

          // if the current IPrincipal is of the same type as our custom

          // security principal then go and check the security right

          if (Thread.CurrentPrincipal is SecurityPrincipal)

          {

              SecurityPrincipal Principal = (SecurityPrincipal)

                                            Thread.CurrentPrincipal;

              // returns whether the user has the permission or not

              return Principal.HasPermission(Permission);

          }

          // if we have a standard IPrincipal in use then we can not check

          // the permission and we always return false

          else

              return false;

    }

      如果你正在创建一个新的应用程序域线程,你不想为每一个线程设置自定义的principal,你可以为每一个新创建的线程创建一个默认的principal。设置默认principal一定要在第一次第一次访问principal之前设置Thread.CurrentPrincipal

    // create the custom principal

    SecurityPrincipal MyPrincipal = SecurityPrincipal.CreateSecurityPrincipal(

                      AppDomain.CurrentDomain.SetThreadPrincipal);

    // set the custom principal as the app domain policy

    AppDomain.CurrentDomain.SetThreadPrincipal(MyPrincipal);

      你设置默认principal,只需要在应用程序域设置一次。在应用程序域设置多次会引发PolicyException异常。

     

      示例代码

     

      示例代码演示的是一个windows form程序,首先需要用户登录(在数据库中已经有两个用户,virusswb,密码和用户名一致)。btnLogon_Click()事件和btnLogin按钮关联,调用DataLayer.CheckUserNameAndPassword().用来验证用户,调用DataLayer.RetrieveUserInformation().来获取用户信息,最后通过调用DataLayer.RetrieveSecurityInformation().来获取用户所属的角色和权限信息,在获取了用户信息、角色信息和权限信息之后,使用SecurityPrincipal.SetSecurityPrincipal()创建一个principalidentity,并且绑定到当前线程。

      从示例中看出用户属于是三个角色,全部的权限,和用户信息,可以检查用户是否属于某一个角色,是否具有某一个权限,CheckSecurityRoles() and CheckSecurityPermissions()返回用户是否属于一个角色,是否有一个权限。logoff 按钮的 LogOff_Click()方法恢复原始的principal,并且返回登陆界面,允许另外一个用户登录,继续前面的处理过程。

      在示例文件夹中你会发现一个叫做RetrieveSecurity.bak的文件,它是数据库的备份文件。恢复数据库,配置app.config文件中的连接字符串。你可以在数据库中添加用户、角色和权限信息。示例展示了在.NET 的角色为基础的安全模型之后,如何实现数据库驱动的验证和安全模型。

     

      总结

      大多数应用都需要通过角色和权限来实现用户验证和安全模型。.NET框架使得这些变得容易,几行代码,就改变了windows账号和安全组的影响。使用自定义的identityprincipal可以很容易的扩展角色为基础的安全框架,示例代码展示的就是如何实现数据库驱动的角色权限系统。

      参考文献

      【1】Role-Based Security  Microsoft

      【2】Introduction to Role-Based Security in .NET  Klaus Salchner

      【3】在Identity 增加自己的属性 部门,并且使用access mdb文件实现角色验证  iHqq

      感谢上面这些机构和作者的无私奉献。

  • 相关阅读:
    Riverside Curio
    bzoj1010 [HNOI2008]玩具装箱toy
    bzoj1898 [Zjoi2005]Swamp 沼泽鳄鱼
    hdu 5435 A serious math problem
    poj2411 Mondriaan's Dream
    bzoj3450 Tyvj1952 Easy
    关于欧拉函数与莫比乌斯函数等一系列积性函数的线性筛
    NOIP后一波总结
    回忆一下电子科技大学春令营
    【算法】背包九讲
  • 原文地址:https://www.cnblogs.com/virusswb/p/1675414.html
Copyright © 2020-2023  润新知