WCF中用户模拟与授权

通常服务器上的一些资源(如文件系统、网络套接字等)需要调用者提供安全口令来决定是否有权访问。而运行服务的宿主进程通常也被限制在一定的权限范围内。为了访问那些特殊资源,通常采用模拟(Impersonation)来让服务宿主进程提升权限以达到可以访问资源的要求。这种情况是提升了服务权限,如果调用者只需很小权限,模拟调用者身份就可以让服务降低权限运行。模拟就是让运行服务的宿主进程使用调用者的身份来替换当前宿主进程使用的身份。所以服务就只能访问调用者被允许访问的资源,以调用者权限运行,这样可以很容易地确保用户只访问了恰当的数据和资源。在WCF中可以通过TokenImpersonationLevel来设置模拟级别。

TokenImpersonationLevel.None:没有模拟级别限制,客户端不使用安全凭证。

TokenImpersonationLevel.Anonymous:客户端不使用安全凭证。

TokenImpersonationLevel.Identification:服务端可以识别客户端身份,但是服务端不允许模拟客户端,服务所做的每件事都必须是用自己的身份完成。

TokenImpersonationLevel.Impersonation:服务端可以识别客户端身份,而且可以模拟客户端。

即使服务宿主被配置为一个权限很小的身份,通过模拟客户端身份,也可以做任何客户端可以做的事。客户端与模拟它身份的服务的区别:当服务与客户端不在同一台机器上是,它将无法像客户端一样访问位于另一台机器上的资源,因为服务模拟无法真的获得客户端的密码。

TokenImpersonationLevel.Delegation:为服务提供了客户端的Kerberos票据。服务可以像客户端那样自由访问任何机器上的资源。

还是写一些简单代码测试,在测试前需要建一测试账号Test,密码:wcf。

如图:

服务契约:

using System.ServiceModel;

namespace IFruitSvc
{
[ServiceContract(Namespace
="http://www.cnblogs.com/qiuwuyu")]
public interface IFruitService
{
[OperationContract]
string GetFruitName();
}
}

服务实现:

using System;
using IFruitSvc;
using System.ServiceModel;
using System.Security.Permissions;
using System.Security.Principal;
using System.Threading;

namespace FruitSvc
{
public class FruitService:IFruitService
{
[OperationBehavior(Impersonation
= ImpersonationOption. Required)]
public string GetFruitName()
{
DisplaySecurityDetails();
return "banana";
}
private void DisplaySecurityDetails()
{
Console.WriteLine(
"Windows Identity:" + WindowsIdentity.GetCurrent().Name);
Console.WriteLine(
"Thread CurrentPrincipal Identity:" + Thread.CurrentPrincipal.Identity.Name);
Console.WriteLine(
"ServiceSecurityContext Current PrimaryIdentity:" +
ServiceSecurityContext.Current.PrimaryIdentity.Name);
Console.WriteLine(
"ServiceSecurityContext Current WindowsIdentity:" +
ServiceSecurityContext.Current.WindowsIdentity.Name);
}
}
}

寄存服务:

using System;
using IFruitSvc;
using FruitSvc;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace WcfSecurityHost
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(FruitService),
new Uri("net.tcp://localhost:8000")))
{
host.AddServiceEndpoint(
typeof(IFruitService), new NetTcpBinding(), "FruitService");

host.Authorization.ImpersonateCallerForAllOperations
= true;
host.Authorization.PrincipalPermissionMode
= PrincipalPermissionMode.UseWindowsGroups;

host.Open();
Console.WriteLine(
"Fruit Service Is Running...");
Console.ReadLine();
}
}
}
}

客户端调用:

using System;
using System.ServiceModel;
using IFruitSvc;

namespace WcfSecurityClient
{
class Program
{
static void Main(string[] args)
{
EndpointAddress epAddr
= new EndpointAddress("net.tcp://localhost:8000/FruitService");
ChannelFactory
<IFruitService> factory = new ChannelFactory<IFruitService>(new NetTcpBinding(), epAddr);
factory.Credentials.Windows.AllowedImpersonationLevel
=
System.Security.Principal.TokenImpersonationLevel.Identification;
IFruitService proxy
= factory.CreateChannel();

Console.WriteLine(proxy.GetFruitName());

Console.ReadLine();
}
}
}

运行结果如下:

修改客户端代码,使服务模拟Test用户身份运行,添加一行代码

//"LiFeng\Test"中LiFeng计算机名,Test用户名 让服务以用户"LiFeng\Test"身份运行
factory.Credentials.Windows.ClientCredential=new System.Net.NetworkCredential(@"LiFeng\Test", "wcf");

此时的执行结果:

可以看出服务运行使用了Test用户的身份。

使用模拟需要的资源会随着客户端的增多而不断增加,这样无法从资源池(如连接池)中获益,而且还增加了管理资源的复杂性,由于需要将资源的访问权限授予最初的客户端身份,从而导致了大量的身份需要管理。如果不管访问者身份,一个服务总是以自己身份运行,将不存在以上问题。所以对资源的访问控制应该使用授权。

下面修改一下代码,进行一些简单测试,用代码理解一下授权

用[PrincipalPermission(SecurityAction.Demand, Name = @"LiFeng\Administrator")]修饰FruitService类中的GetFruitName()方法。也就是把这个方法的调用授权给计算机名为LiFeng用户名为Administrator的用户,其他用户无权访问。

运行程序,将抛出"Request for principal permission failed."因为此时还是用账号“LiFeng\Test”,它无权访问。改为[PrincipalPermission(SecurityAction.Demand, Name = @"LiFeng\Test")]程序将正常运行。只是为了测试,如果程序中使用授权,最好授权角色,而不是授权每一个用户。

posted @ 2011-04-25 08:17  秋无语  阅读(1195)  评论(0编辑  收藏  举报