不好意思,标题太长了。
这个是关于WCF配置和安全性的。我有以下场景:
- 一个服务器(IIS)
- N客户端(WPF应用程序)
- 用于客户端和服务器之间通信的web服务
- 所有东西都在同一个局域网和同一个域
- 我需要一些双工通信,以便服务器可以通知所有客户端(没有轮询,但使用WCF回调)
- 用户凭据(登录/密码)由活动目录 管理
- 用户身份验证是"每个用户",而不是"每个windows帐户"或"每个机器"
理想情况下,我希望有两种不同的服务:
需要一个WCF会话,所以我可以使用回调:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IService1Callback))]
public interface IService1
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Subscribe();
[OperationContract(IsOneWay = false, IsTerminating = true)]
void Unsubscribe();
}
没有,所以我可以使用好的老方法来编写无状态、高效和可维护的服务:每个WCF调用都经过身份验证、授权,并在服务器端产生一个新的服务实现实例:
[ServiceContract]
public interface IService2
{
[OperationContract]
int DoSomeStuff();
}
问题是,我们希望每个客户端都对活动目录进行身份验证。因此,在客户端应用程序启动时,每个客户端必须指定登录名、密码和域。问题是AD认证非常耗时:
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "theDomain"))
{
if (!pc.ValidateCredentials("theUser", "thePassword"))
{
throw new SecurityException();
}
}
我的第一个想法是使用IService1
服务(负责发送回调)作为认证服务:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IService1Callback))]
public interface IService1
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Login(string login, string password, string domain);
[OperationContract(IsOneWay = false, IsTerminating = true)]
void Logout();
}
因此,在Login
操作实现中,我们可以根据AD检查一次用户登录/密码,并依赖WCF会话来处理来自客户端的下一个WCF请求。问题是,我不想对IService1
服务提出其他请求,因为会话模式是Required
, InstanceContextMode
是PerSession
(是的,丑陋的客户端-代理单例模式,我想避免其他操作,而不仅仅是登录/注销/客户端通知)。
所以问题是:我如何构建和配置我的服务,所以:
- 我不需要在每个WCF请求上请求AD
- 我有一些服务器回调
- 对于回调服务(不是"实际的"
IService2
服务),我只有单例模式/会话所需/每个会话的实例上下文
我想wsHttpBinding
为IService2
和netTcpBinding
为IService1
。关于传输安全性的选择(传输或消息)和凭证类型的选择(Windows, UserName,…)?)我不确定。
任何帮助都将非常感激。
你可以试试这个:
创建一个自定义消息检查器,它接受两个参数- UserName和PWD,您将在客户端第一次访问您的主机时在AD服务器上进行身份验证。但是在向客户端返回响应之前,添加一个有时间限制的cookie。当客户端再次请求时,检查请求的OperationContext中是否存在该cookie,并跳过AD身份验证。
关于这个解决方案有两件事要知道。首先,当您使用消息检查器时,每个请求(不仅仅是Login)都会命中过程。这就是它们的工作原理。但是由于cookie检查,只要cookie有效,就可以跳过后续的AD检查。并且它消除了登录过程的必要性。
像这样:
public class SchemaLoggingMessageInspector : Dispatcher.IDispatchMessageInspector
{
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext) {
var cookieHeader = WebOperationContext.Current.IncomingRequest.Headers[System.Net.HttpRequestHeader.Cookie];
if (!String.IsNullOrEmpty(cookieHeader)){
var match = cookieHeader.Split(';').Select(cookie => cookie.Split('=')).FirstOrDefault(kvp => kvp[0] == "BigCookie");
if (match != null)
{
return True
}
}
if (WcfBaseFunctionality.eSecurityType == SecurityEnum.authentication) {
string username = AuthenticationBehavior.GetHeaderData("Username");
string password = AuthenticationBehavior.GetHeaderData("Password");
if (ValidateAuthentication(username, password){
WebOperationContext.Current.OutgoingResponse.Headers[System.Net.HttpResponseHeader.SetCookie] = Cookie="BigCookie";
return True;
}else{
return False;
};
}
return False;
}
private bool ValidateAuthentication(string UserName, string PWD)
{
//add code to check ID/password against AD server
return true;
}
}
就客户端回调而言,我从未尝试过。对不起。祝你的web服务顺利。
如果我没理解错的话:
- 您需要服务(1)
- 负责认证,令牌,以及
- 还提供每个会话的回调
- 你需要一个实际的worker服务
- 可以进行处理
- 可以发起回调
- 只有经过认证才能工作
在我看来,身份验证是与会话问题相耦合的,这样就不是困难的问题(如果同时需要联邦(基于令牌的身份)和AD,将使用ADFS)。我试着用两个简单的服务
- service1: wsHttpBinding (for sake of session)
- service2: basicHttpBinding(为了简单起见,也可以是net tcp)
考虑到处理的结束(service2的职责)应该触发回调(service1的职责),没有办法避免共享某种回调信息(这也意味着共享会话id或任何标识service1活动会话实例的内容)。
我尝试了最简单的方法,使用缓存来存储针对会话键的回调实例。(我知道很多反例,说明为什么这是邪恶的,但它只适用于poc)。这样两个服务的生命周期就解耦了,回调仍然可以被调用。
我确信生成适当的令牌并将令牌检查绑定到处理服务(ADFS及其消费者)都有很好的文档记录。
我仍然认为关键是在这里使用共享回调。
如果我完全误解了你的问题,请告诉我。谢谢,尼科莱