[WCF权限控制]从两个重要的概念谈起:Identity与Principal[上篇]
在安全领域,认证和授权是两个重要的主题。认证是安全体系的第一道屏障,守护着整个应用或者服务的第一道大门。当访问者叩门请求进入的时候,认证体系通过验证对方提供凭证确定其真实身份。作为看门人的认证体系,只有在证实了访问者的真实身份的情况下才会为其打开城门,否则将之举之门外。
当访问者入门之后,并不意味着它可以为所欲为。为了让适合的人干适合的事,就需要授权机制为具体的人设置具体的权限,并根据这些权限设置决定试图调用的操作或者访问的资源对该访问者是否是安全的。对于一个安全保障体系来说,授权是目的。但是授权的执行是假定已经通过认证体现确定了访问者真实身份,因为用于进行授权采用的权限集是基于真个确定的身份的。在真正进入对WCF授权的具体介绍之前,我们有必要来了解一下这个“身份”的问题。
目录
一、IIdentity
二、WinodwsIdentity
三、GenericIdentity
四、X509Identity
五、服务安全上下文中的身份
一、IIdentity
在.NET的安全应用编程接口中,身份通过System.Security.Principal.IIdentity接口表示。从下面表示IIdentity接口定义的代码片断中,我们可以看到这个接口定义其实很简单,它具有如下三个只读属性:
- Name:身份所代表的用户的名称;
- IsAuthenticated:身份所代表的用户是否经过认证;
- AuthenticationType:身份认证所采用的类型。
1: public interface IIdentity
2: {
3: string AuthenticationType { get; }
4: bool IsAuthenticated { get; }
5: string Name { get; }
6: }
通过IIdentity表示的身份是基于某种认证类型的,不同类型的认证往往对应于不同的身份类型。以ASP.NET认证为例,如果我们采用Forms认证,那么认证后的身份通过一个FormsIdentity对象表示。而Windows Live Passport认证对应的具体身份类型则是PassportIdentity。在这里我们着重介绍一下如下三种身份类型:WindowsIdentity、GenericIdentity和X509Identity。
二、WindowsIdentity
WindowsIdentity定义在System.Security.Principal命名空间下。顾名思义,WindowsIdentity用于表示一个基于Windows认证的身份。一个采用WindowsIdentity定义的Windos身份具有一系列的属性,它们主要定义在如下的代码片断中。
1: public class WindowsIdentity : IIdentity, ...
2: {
3: //其他成员
4: public virtual string Name { get; }
5: public string AuthenticationType { get; }
6:
7: public virtual bool IsAuthenticated { get; }
8:
9: public IdentityReferenceCollection Groups { get; }
10: public virtual bool IsAnonymous { get; }
11: public virtual bool IsGuest { get; }
12: public virtual bool IsSystem { get; }
13: }
对于用于表示认证类型的AuthenticationType属性来说,在工作组模式下返回NTLM。对于域模式,如果操作系统是Vista或者以后的版本,该属性返回Negotiate,表示采用SPNEGO认证协议。而对于之前的Windows版本,则该属性值为Kerberos。
Groups属性返回WindowsIdentity对应的Windows帐号所在的用户组(User Group),而IsGuest则用于判断Windows帐号是否存在于Guest用户组中。IsSystem属性则表示Windows帐号是否是一个系统帐号。
如果你对ASP.NET的安全有一定的了解,应该知道我们可以对IIS进行相应的配置是ASP.NET应用支持匿名用户。也就是说,用户无需提供具体的用户凭证,而是以匿名的方式登录到ASP.NET站点中。对于匿名登录,IIS实际上会采用一个预先指定的Windows帐号进行登录。而在这里,IsAnonymous属性就表示该WindowsIdentity对应的Windows帐号是否是匿名帐号。
对于匿名身份的问题,在这里还有一点值得补充一下。WindowsIdentity定义了如下一个静态的GetAnonymous方法用于返回一个表示匿名身份的WindowsIdentity对象。但是这仅仅是一个空的WindowsIdentity对象而以,并不对应着某个确定的Windows帐号。
1: public class WindowsIdentity : IIdentity, ...
2: {
3: //其他成员
4: public static WindowsIdentity GetAnonymous()
5: }
任何一个具体的Windows进程总是运行在一个确定的安全身份下。如果你手工启动一个.exe文件,被开启的进程会运行在基于当前登录帐号的身份下。如有你同时拥有多个Windows帐号,你可以通过“Run As”的方式选择一个不同于当前登录帐号的身份去运行某个.exe文件。而对于很多的Windows服务,它们大多运行在某个系统帐号下。比如我们熟悉的IIS(IIS6或者之后的版本)在默认的情况下就运行在Network Service这个系统帐号下面。当一个线程在这个进程中被创建并启动的时候,进程的安全身份会自动附加到线程上。WindowsIdentity为我们提供了如下一个GetCurrent静态方法返回基于当前线程/进程的WindowsIdentity。
1: public class WindowsIdentity : IIdentity, ...
2: {
3: //其他成员
4: public static WindowsIdentity GetCurrent();
5: }
三、 GenericIdentity
虽然对于这些我们常用的认证类型,比如Windows认证、Forms认证和Windows Live Passport认证,都具有对应的安全身份类型。如果我们采用自定义的认证方式,是否意味着我们也需要定义一个实现了IIdentity接口的类型呢?实际上是不需要的,我们可以直接使用GenericIdentity这个类型。
正如名称所体现的一样,GenericIdentity为我们定义了一个一般性的安全身份。GenericIdentity的定义非常简单,仅仅实现了定义在IIdentity接口的三个只读属性而以。我们可以通过指定用户名或者用户名与认证类型来创建一个GenericIdentity对象。下面的代码片断体现了GenericIdentity的整个定义。
1: public class GenericIdentity : IIdentity
2: {
3: public GenericIdentity(string name);
4: public GenericIdentity(string name, string type);
5:
6: public virtual string AuthenticationType { get; }
7: public virtual bool IsAuthenticated { get; }
8: public virtual string Name { get; }
9: }
由于GenericIdentity的IsAuthenticated属性是只读,也不同通过存储过程对其进行初始化,那么如何确定一个通过GenericIdentity对象表示的安全身份是否已经通过认证了呢?实际上,GenericIdentity采用很简单的逻辑来判断其自身是否经过认证:如果用户名不为空,IsAuthenticated返回True,否则返回False。下面给出的代码可以验证这一点。
1: var anonymousIdentity = new GenericIdentity("");
2: var authenticatedIdentity = new GenericIdentity("Foo");
3: Debug.Assert(anonymousIdentity.IsAuthenticated == false);
4: Debug.Assert(authenticatedIdentity.IsAuthenticated == true);
四、X509Identity
通过前面一章的介绍,我们知道了WCF具有三种典型的认证方式:Windows认证、用户名/密码认证和证书认证。认证的方式决定了安全身份的类型,对于Windows认证和用户名/密码认证,认证后的安全身份分别由一个WindowsIdentity和GenericIdentity表示。但是对于证书认证,则对应着另一种安全身份类型:X509Identity。
X509Identity定义在程序集System.IdentityModel中,对应的命名空间是System.IdentityModel.Claims。从下面给出的定义我们可以看出X509Identity仅仅是一个内部(Internal)类型。
1: internal class X509Identity : GenericIdentity, IDisposable
2: {
3: //其他成员
4: public X509Identity(X500DistinguishedName x500DistinguishedName);
5: public X509Identity(X509Certificate2 certificate);
6:
7: public X509Identity Clone();
8: public void Dispose();
9: public override string Name { get; }
10: }
X509Identity直接继承自GenericIdentity。我们可以通过传入一个X509Certificate2对象或者以X500DistinguishedName对象表示的证书的标识名称来创建X509Identity。X509Identity重写了GenericIdentity的Name属性,最终作为名称的返回的是证书的主题名称和指纹的组合,<<主题名称>>; <<指纹>>(分号之后具有一个空格,比如:CN=Foo; 12BA3675C89BD7FE00E3F7E92A620749FB9E6D89)。X509Identity对象的AuthenticationType属性为“X509”。
五、服务安全上下文中的身份
当服务安全开始的情况,服务端在经过认证之后会创建一个上下文用以存储基于当前服务调用相关的安全相关的信息,其中就包含了代表被认证客户端的安全身份。这个上下文被称为服务安全上下文,通过类型ServiceSecurityContext表示。
1: public class ServiceSecurityContext
2: {
3: //其他成员
4: public static ServiceSecurityContext Current { get; }
5: public IIdentity PrimaryIdentity { get; }
6: public WindowsIdentity WindowsIdentity { get; }
7:
8: public bool IsAnonymous { get; }
9: public static ServiceSecurityContext Anonymous { get; }
10: }
你可以通过两种方式获取当前的ServiceSecurityContext,一种是通过ServiceSecurityContext的静态只读属性Current,另一种则是通过当前OperationContext的ServiceSecurityContext属性。实际上通过这两种方式得到的是同一个ServiceSecurityContext。ServiceSecurityContext对象的同一性可以通过下面的代码来验证。
1: var securityContext1 = OperationContext.Current.ServiceSecurityContext;
2: var securityContext2 = ServiceSecurityContext.Current;
3: Debug.Assert(object.ReferenceEquals(securityContext1, securityContext2));
ServiceSecurityContext具有两个表示安全身份的属性PrimaryIdentity和WindowsIdentity,它们都代表当前客户端的身份。对于Windows认证,这两个属性返回同一个WindowsIdentity对象。不过需要注意的是,这是所说的Windows认证实际上包括如下三种情况:
- 客户端凭证为Windows凭证;
- 客户端凭证为用户名/密码凭证,并采用Windows认证模式;
- 客户端凭证为X.509证书凭证,并允许与Windows帐号进行映射。
而对于不属于上述三种情况下的非Windows凭证,当前ServiceSecurityContext的WindowsIdentity属性返回Null,而PrimaryIdentity属性则因客户端凭证类型和认证方式有所区别。具体来说,如果客户端凭证为用户名/密码凭证,并采用Membership和Custom认证模式,则在成功认证的情况下PrimaryIdentity的属性返回一个以用户名作为名称的GenericIdentity。如果客户端凭证为X.509证书凭证,但不采用Windows帐号映射机制,则PrimaryIdentity的属性返回的是一个X509Identity。
对于匿名客户端(客户端凭证类型为None),PrimaryIdentity返回的是一个空的GenericIdentity,IsAnonymous返回True。你通过静态属性Anonymous可以返回一个匿名ServiceSecurityContext。下满的表格体现了成功认证后当前ServiceSecurityContext的PrimaryIdentity与客户端凭证类型以及认证模式之间的关系。
从两个重要的概念谈起:Identity与Principal[上篇]
从两个重要的概念谈起:Identity与Principal[下篇]