您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
WCF4.0 –- RESTful WCF Services(二)
 
作者:机器人 来源:CSDN 发布于 2015-7-13
  3091  次浏览      14
 

WCF4.0 –- RESTful WCF Services (4) (Basic Security)

在REST架构的WCF服务中,它不像一般的WCF服务绑定,有配套的安全模式,实现起来那么简单。REST WCF服务只能在传输层加密,而一般的WCF 服务可以在消息层加密。因此 REST WCF服务启用ASP.NET兼容模式后,它的安全是由ASP.NET来保证的。本篇文章主要介绍在 REST WCF 中如何实现最简单的 Username 验证。

在SOAP协议的WCF中,可以通过SOAPHeader(MessageHeader)来实现用户名密码的传输,早在WebService时代我们就这么用过了。在REST WCF中,我们可以利用 HttpHeader 来完成这一目标。 (你可不会想在每个服务契约里加上用户和密码的参数吧...)

首先在服务中加入如下方法用于校验,Header的信息:如果 Header 中 Authorization 的字符串不是"fangxing/123" 那么就将返回 405 MethodNotAllowed 的错误。这个字符串的内容可以自定义,反正服务端根据某种规则检查这个字符串。

private bool CheckAuthorization()  
{
var ctx = WebOperationContext.Current;
var auth = ctx.IncomingRequest.Headers[HttpRequestHeader.Authorization];
if (string.IsNullOrEmpty(auth) || auth != "fangxing/123")
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed;
return false;
}
return true;
}

然后在每一个服务契约的实现中,都去调用它。

[WebGet(UriTemplate = "All")]
public List<Task> GetTask()
{
if (!CheckAuthorization())
return null;
return GetData();
}
[WebGet(UriTemplate = "{taskId}")]
public Task GetTaskById(string taskId)
{
if (!CheckAuthorization())
return null;
return GetData().FirstOrDefault(t => t.Id==taskId);
}

现在的服务,如果直接通过浏览器访问,将得到 405 MethodNotAllowed 的错误:

客户端只要相应的验证信加到 RequestHeader 中去,就可以访问了。客户端可以使用单例模式设计 Client 对象。
这样就不用每次调用都去加验证信息了。

var url = "http://localhost:3433/TaskService/All";  
var client = new HttpClient();
client.DefaultHeaders.Add("Authorization", "fangxing/123");
var resp = client.Get(url);

这里使用的是 Microsoft.Http.HttpClient (WCF REST Starter Kit) 而非 System.Net.WebClient

回头看服务端代码,每个服务实现中都需要加上 CheckAuthorization() 是不是很烦?

OK,我们知道这个 REST WCF服务是承载在一个Web Application上的, 通过往 RouteTable 中注册 WebServiceHostFactory 来激活服务对象的。 那么只要对这个 WebServiceHostFactory 做些“手脚”,就可以实现服务端验证的统一拦截,代码如下。(一般的 WCF 也可以利用此方法对 MessageHeader 进行拦截校验)

public class SecureWebServiceHostFactory : WebServiceHostFactory  
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var host = base.CreateServiceHost(serviceType, baseAddresses);
host.Authorization.ServiceAuthorizationManager = new MyServiceAuthorizationManager();
return host;
}

public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var host = base.CreateServiceHost(constructorString, baseAddresses);
host.Authorization.ServiceAuthorizationManager = new MyServiceAuthorizationManager();
return host;
}
}

public class MyServiceAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
var ctx = WebOperationContext.Current;
var auth = ctx.IncomingRequest.Headers[HttpRequestHeader.Authorization];
if (string.IsNullOrEmpty(auth) || auth != "fangxing/123")
{
ctx.OutgoingResponse.StatusCode = HttpStatusCode.MethodNotAllowed;
return false;
}
return true;
}
}

RegisterRoutes 里的工厂类也需要相应的修改下:

var securewebServiceHostFactory = new SecureWebServiceHostFactory();  
RouteTable.Routes.Add(new ServiceRoute("TaskService",
securewebServiceHostFactory, typeof(TaskService)));

这样服务端代码就可以去掉 CheckAuthorization() 而把验证工作都交给 SecureWebServiceHostFactory 了。

这种验证方式,其实也是现在 Windows Auzer Access Control 的原型。 只不过这个 Authoriztion 的服务是专门的Services罢了。

1. 客户端先从发布令牌的服务获取令牌;

2. 客户端拿着令牌提交到现在的服务;

3.服务端将客户端令牌拿到发布令牌的服务上校验。

WCF4.0 –- RESTful WCF Services (实例) (并发同步服务 SyncService)

最近写自动化测试时遇到一个问题: 我们在进行一个并发测试的过程中,需要所有客户端测试代码,在某个时机同步。回想所学到的,线程同步手段很多了,同一台PC上的进程间同步也可以通过Metux实现,多PC的时候怎么办。因为最近在学习REST WCF,自然想到它,用它来做个同步服务,即可以解决多线程,多进程,多PC同步,还可以支持跨语言,真是一举多得。(类似的解决方案还有PNUNIT,它是通过.Net Remoting实现的,因为它还要写配置,还要起Lancher/Agent,有点烦)。

1. SyncService 的主要功能——Barrier(栏栅):

借用PNUNIT的概念Barrier,也就是异步过程中的同步点,进到Barrier里的所有对象都要等待其他对象进入。这些对象可以是不同的线程,进程(不同PC,不同语言实现的客户端),过程如下图:3个客户端启动之后,有快有慢,但是在Barrier处进行一次同步,先到的等待后到的。

举个实际例子: 假如我们要实现两个客户端通信的功能的测试,必须是两个客户端同时上线。那么我们可以在代码中设计一个barrier,让双方都确认上线之后,再进行通信测试。

(1) 准备Barrier

这里有点要特别说明的地方var init = SyncService.Init("Barrier_Logon", "Client1", "Client2");  
// 启动Client1
Process.Start("Client1.exe");
// 启动Client2
Process.Start("Client2.exe");

(2) Client1

// client1登录  
var client1 = Login("Client1");
// 同步,等待Client2登录
var enter = SyncService.Enter("Barrier_Logon", "Client1");
// client1 和 client2 相互通信 ...

(3) Client2 和 Client1 类似

// client2登录  
var client2 = Login("Client2");
// 同步,等待Client1登录
var enter = SyncService.Enter("Barrier_Logon", "Client2");
// client1 和 client2 相互通信 ...

2. SyncService 的消息交换功能——SetMessage/GetMessage:

我们还可以通过SyncService中的消息容器进行消息传递。如下图:

在异步的两段代码中,设置同步点,保证 GetMessage 是在 SetMessage 之后发生。这一点是并行测试中是很常见的处理。

Client1的代码:

// 设置消息给client2  
var set = SyncService.SetMessage("Barrier", "key", "hello client2");
// 进入Barrier, 等待client2
var enter = SyncService.Enter("Barrier", "Client1");

Client2的代码:

// 进入Barrier,等待client1  
var enter = SyncService.Enter("Barrier", "Client2");
// 取得消息
var get = SyncService.GetMessage("Barrier", "key");
// 确认获得消息,是"hello client2"
Assert.AreEqual(get, "hello client2");

3. SyncService的实现

如果上面的并行处理代码理解了的话,SyncService的实现就很好推断出来了。服务端维护一个Dictionary<string, SyncGroup>的容器,每个客户端Enter时,调用对应的ManualResetEvent.Set()解锁。然后WaitAll其他的ManualResetEvent,从而实现同步。

using System;  
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.Text;
using System.Threading;
using System.Runtime.Serialization;

namespace SyncService
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class SyncService
{
private static Dictionary<string, SyncGroup> _syncPool = new Dictionary<string, SyncGroup>();

[WebGet(UriTemplate="Init/{barrier}/{targetnames}")]
public string Init(string barrier, string targetnames)
{
var ctx = WebOperationContext.Current;
try
{
lock (_syncPool)
{
_syncPool[barrier] = new SyncGroup();
var syncGroup = _syncPool[barrier];
var targets = targetnames.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Array.ForEach(targets, t => syncGroup.ResetEventDict.Add(t, new ManualResetEvent(false)));
}
return "ok";
}
catch (Exception ex)
{
return ex.Message;
}
}

[WebGet(UriTemplate = "Enter/{barrier}/{targetname}/{timeout=60000}")]
public string Enter(string barrier, string targetname, string timeout)
{
var ctx = WebOperationContext.Current;
try
{
var syncObj = _syncPool[barrier];
var target = syncObj.ResetEventDict[targetname];
target.Set();
var intTimeout = int.Parse(timeout);
var success = WaitHandle.WaitAll(syncObj.ResetEventDict.Values.ToArray(), intTimeout);
if (success)
return "ok";
else
return "timeout";
}
catch (Exception ex)
{
return ex.Message;
}
}

[WebGet(UriTemplate = "SetMessage/{barrier}/{key}/{message=null}")]
public string SetMessage(string barrier, string key, string message)
{
var ctx = WebOperationContext.Current;
try
{
var syncObj = _syncPool[barrier];
lock (syncObj)
{
var query = syncObj.Messages.FirstOrDefault(m => m.Key == key);
syncObj.Messages.Remove(query);
var messageInfo = new MessageInfo
{
BarrierName = barrier,
Key = key,
Message = message,
UpdateDateTime = DateTime.Now
};
syncObj.Messages.Add(messageInfo);
}
return "ok";
}
catch (Exception ex)
{
return ex.Message;
}
}

[WebGet(UriTemplate = "GetMessage/{barrier}/{key}")]
public string GetMessage(string barrier, string key)
{
var ctx = WebOperationContext.Current;
try
{
var syncObj = _syncPool[barrier];
var query = syncObj.Messages.FirstOrDefault(m => m.Key == key);
return query.Message;
}
catch (Exception ex)
{
return ex.Message;
}
}

[WebGet(UriTemplate = "ListMessages/{barrier=all}", ResponseFormat=WebMessageFormat.Xml)]
public List<MessageInfo> ListMessages(string barrier)
{
var ctx = WebOperationContext.Current;
try
{
var messages = new List<MessageInfo>();
if (barrier == "all")
_syncPool.Values.ToList().ForEach(t => messages.AddRange(t.Messages));
else
messages = _syncPool[barrier].Messages;
return messages;
}
catch
{
return null;
}
}

[WebGet(UriTemplate="Check", ResponseFormat=WebMessageFormat.Xml)]
public string Check()
{
return "Welcome to the SyncService! " +
DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString();
}

}

[DataContract]
[KnownType(typeof(MessageInfo))]
public class SyncGroup
{
internal Dictionary<string, ManualResetEvent> ResetEventDict { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public List<MessageInfo> Messages { get; set; }
[DataMember]
public Dictionary<string, string> States { get; set; }

public SyncGroup()
{
Messages = new List<MessageInfo>();
ResetEventDict = new Dictionary<string, ManualResetEvent>();
}
}

[DataContract]
public class MessageInfo
{
[DataMember]
public string BarrierName { get; set; }
[DataMember]
public string Key { get; set; }
[DataMember]
public string Message { get; set; }
[DataMember]
public string Identity { get; set; }
[DataMember]
public DateTime UpdateDateTime { get; set; }
}
}

默认使用JSON格式,另外为了查看当前的同步的状况和消息,可以通过 ListStates/ListMessages 查看。

(1) 初始化Barrier则发送: http://server/SyncService/Init/MyBarrier/Client1,Client2

(2) 客户端进入Barrier则发送: http://server/SyncService/Enter/MyBarrier/Client1/10000 (最后是timeout设定)

(3) 设置消息则发送: http://server/SyncService/SetMessage/MyBarrier/Key/MessageContent

(4) 取得消息则发送: http://server/SyncService/GetMessage/MyBarrier/Key

(5) 查看所有的“锁”则发送:http://server/SyncService/ListStates (或者指定某个Barrier: /MyBarrier)

(6) 查看所有的消息则发送:http://server/SyncService/ListMessages(或者指定某个Barrier: /MyBarrier)

(7) 清空所有SyncGroup则发送:http://server/SyncService/Restart

是的,全部的操作全部是 HttpRequest 的"GET", 因此各种客户端都可以轻松调用,很方便。 (用WCF创建这样一个服务也非常简单全部代码一百多行,正所谓天下武功无快不破:)

上篇:WCF4.0 –- RESTful WCF Services(一)

   
3091 次浏览       14
 
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
 
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
 
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

使用decj简化Web前端开发
Web开发框架形成之旅
更有效率的使用Visual Studio
MVP+WCF+三层结构搭建框架
ASP.NET运行机制浅析【图解】
编写更好的C#代码
10个Visual Studio开发调试技巧
更多...   

.NET框架与分布式应用架构设计
.NET & WPF & WCF应用开发
UML&.Net架构设计
COM组件开发
.Net应用开发
InstallShield

日照港 .NET Framework & WCF应用开发
神华信息 .NET单元测试
北京 .Net应用软件系统架构
台达电子 .NET程序设计与开发
赛门铁克 C#与.NET架构设计
广东核电 .Net应用系统架构
更多...