现在的位置: 首页 > 综合 > 正文

WCF4.0 —— Routing Service 自定义Filter控制访问权限

2013年08月19日 ⁄ 综合 ⁄ 共 3215字 ⁄ 字号 评论关闭
接着上一篇的实例,我们通过Routing Service使用了 filterType="EndpointName" 来转发客户端的请求,映射到内部多个服务上。
但问题又来了,比如有2个内部服务A,B。有N个客户端,并不是所有的客户端同时都有对A,B的访问权限,
有些客户端可以访问A,有些客户端可以访问B,如何控制客户端请求的权限呢?了解WCF认证机制的童鞋们大多数会想到通过服务A,B
自身的认证功能去屏蔽非法的访问,甚至更优秀的实践是抽出共通的消息拦截,将A,B的认证统一处理。本篇文章则要在 RoutingService 的 Filter 上做做功夫。

我们要实现的功能,如下图所示:

即 User A 只能访问 Service1, User B 只能访问 Service2。这里要利用自定义 Filter 实现对用户名的检查,自定义Filter需要继承 MessageFilter
形如:

public class CustomMessageFilter : MessageFilter
    {
        private string _matchData;
        public CustomMessageFilter(string matchData)
        {
            this._matchData = matchData;
        }        

        public override bool Match(System.ServiceModel.Channels.Message message)
        {
            return message.Headers.Action.IndexOf(this._matchData) > -1;
        }

        public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
        {
            throw new NotImplementedException();
        }
    }

这里需要注意的是,RoutingService 中配置了多个 Endpoint,直接返回 true, false 的判断显然不行,还需要修改客户端请求映射到内部的 Endpoint 上。
因此通过直接继承 EndpointNameMessageFilter 来实现最简单~ 
* 该示例中使用 Windows 集成认证,需要在服务端Windows上建立两个账号:Foo 和 Bar。
如果请求的Action是Service1 则只有 Foo 可以使用,Service2 只有 Bar 可以使用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel.Dispatcher;
using System.ServiceModel;

namespace WcfRoutingService
{
    public class CustomerFilter : EndpointNameMessageFilter
    {
        private string _matchData;

        public CustomerFilter(string matchData) : base(matchData)
        {
            _matchData = matchData;
        }
        
        public override bool Match(System.ServiceModel.Channels.Message message)
        {
            var username = OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;            
            var usernameWithoutDomain = username.Split('\\')[1];

            if (message.Headers.Action.StartsWith("http://tempuri.org/IService1/"))
            {
                if (usernameWithoutDomain == "Foo")
                {
                    return base.Match(message);
                }
            }
            else if (message.Headers.Action.StartsWith("http://tempuri.org/IService2/"))
            {
                if (usernameWithoutDomain == "Bar")
                {
                    return base.Match(message);
                }
            }
            return false;
        }

        public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
        {
            return base.Match(buffer);
        }
    }
}

修改Routing Service的配置文件中 routing/filters/filter 里的内容:

<filters>
  <filter name="Service1Filter" filterType="Custom" customType="WcfRoutingService.CustomerFilter, WcfRoutingService" filterData="service1Endpoint" />
  <filter name="Service2Filter" filterType="Custom" customType="WcfRoutingService.CustomerFilter, WcfRoutingService" filterData="service2Endpoint" />
</filters>

将 filterType 改为 Custom, customType 指向上面的实现类。整体如下图:

客户端调用:

 class Program
 {
     static void Main(string[] args)
     {
        
         Action<string, string> testWithUser = (username, passwd) =>
             {
                 var credential = new NetworkCredential();
                 credential.Domain = "P-FANGXING";
                 credential.UserName = username;
                 credential.Password = passwd;

                 try
                 {
                     // call service1
                     using (var service1client = new WcfService1.Service1Client("service1Endpoint"))
                     {
                         service1client.ClientCredentials.Windows.ClientCredential = credential;
                         var result = service1client.GetData(300);
                         Console.WriteLine(result);
                     }
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine("Service1 -- User[{0}] Error.", username);
                 }

                 try
                 {
                     // call service2
                     using (var service2client = new WcfService2.Service2Client("service2Endpoint"))
                     {
                         service2client.ClientCredentials.Windows.ClientCredential = credential;
                         var result = service2client.GetData(100);
                         Console.WriteLine(result);
                     }
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine("Service2 -- User[{0}] Error.", username);
                 }
             };
         // UserA calling...
         testWithUser("Foo", "abc@123");

         // UserB calling...
         testWithUser("Bar", "abc@123");

         Console.Read();
     }
 }

抱歉!评论已关闭.