我正在做一些单元测试,并在 WCF 中遇到了一个有趣的问题。我有一个使用 的服务wsHttpBinding
,配置如下:
<bindings>
<wsHttpBinding>
<binding name="wsHttpUnsecured">
<security mode="None">
<transport clientCredentialType="None" />
<message clientCredentialType="None" />
</security>
</binding>
</wsHttpBinding>
该服务的实现是:
public void DoWork()
{
OperationContext o = OperationContext.Current;
if (o == null) return;
if (o.ServiceSecurityContext.AuthorizationContext.Properties.ContainsKey("x"))
throw new ApplicationException();
o.ServiceSecurityContext.AuthorizationContext.Properties.Add("x", "x");
}
它在这里所做的只是检查操作上下文,如果 AuthorizationContext 中没有“X”,则添加一个。如果已经有一个“X”,则异常。(这被严格设置为一个简单的测试。在正常使用中,这将在自定义 AuthorizationManager 和 Token Authenticator 中发生)。
所以基本上,如果我们在同一个 operationContext 和 AuthorizationContext 中多次调用该方法,那么我们会看到一个异常。
现在,这是我的测试夹具。
[Test]
public void CallTwice()
{
using (var cli1 = new TestBusinessClient())
{
cli1.DoWork();
cli1.Close();
}
using (var cli2 = new TestBusinessClient())
{
cli2.DoWork();
cli2.Close();
}
}
因此,浏览运行时发生的情况:
- 一个新
TestBusinessClient
的被创建。 - 它调用
DoWork()
. DoWork()
在 中找不到“X”AuthorizationContext.Properties
。DoWork()
将“X”添加到AuthorizationContext.Properties
.- 测试方法处理第一个客户端。
- 新的第二个
TestBusinessClient
被创建。 - 它调用
DoWork()
. DoWork()
确实在上次调用的属性中找到“X”。DoWork()
抛出异常。
所以我的问题是;当新客户端连接到同一服务时,为什么OperationContext
andAuthorizationContext
没有被杀死?我确实理解wsHttpBinding
默认情况下在调用之间使用会话上下文,但我认为会话将是每个客户端的。当我连接到客户端的新实例时,我希望 WCF 会话及其上下文全部更新。
任何人有任何想法或想法?此处所需的结果是在两个单独的客户端AuthorizationContext.Properties
的两次调用之间重置。DoWork()
更新 1
我尝试设置服务PerCall
和PerSession
,但都没有改变。
我还尝试了开启和关闭可靠消息传递的服务,但都没有改变任何东西。
我还在第一次调用和第二次调用时保存了我的 operationContext,并比较了两者:
OperationContext first; // context from first call to DoWork()
OperationContext second; // context from second call to DoWork()
(first == second) = false
(first.ServiceSecurityContext == second.ServiceSecurityContext) = false
(first.ServiceSecurityContext.AuthorizationContext == second.ServiceSecurityContext.AuthorizationContext) = true
因此,看起来操作上下文已更改/重新创建,但AuthorizationContext
在每个后续服务调用中设置相同。
更新 2
这是所有服务器端的东西:
[ServiceContract]
public interface ITestBusiness
{
[OperationContract(Action = "*", ReplyAction = "*")]
string DoWork();
}
public class TestBusiness : ITestBusiness
{
public string DoWork()
{
System.ServiceModel.OperationContext o = System.ServiceModel.OperationContext.Current;
if (o != null)
{
if (o.ServiceSecurityContext.AuthorizationContext.Properties.ContainsKey("x"))
throw new ApplicationException();
else
o.ServiceSecurityContext.AuthorizationContext.Properties.Add("x", "x");
}
}
return "";
}
作为健全性检查,我做了以下工作:
- 启动 WCF 服务器的一个实例(使用 Cassini/集成 VS 2008 服务器)。
- 将测试夹具减少到只有 1 次调用
DoWork()
. TestDriven.NET
在 VS 2008 中运行测试。.dll
从NUnit's
独立的 GUI 工具打开相同的测试,然后运行它。
第一次测试通过,第二次失败。因此,这似乎是纯粹的服务器端,因为从 2 个不同进程运行相同的服务调用最终会得到相同的AuthorizationContext
.
我开始怀疑 WCF 内部的某些东西是否仍然停留在WindowsAuthentication
并重用相同的Auth
上下文,因为我使用相同的用户名登录到同一个域?我的服务设置为使用自定义AuthorizationManager
:
<serviceBehaviors>
<behavior name="myBehavior">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceAuthorization principalPermissionMode="Custom" serviceAuthorizationManagerType="My.Namespace.CustomAuthMgr" />
</behavior>
在哪里My.Namespace.CustomAuthMgr
延伸ServiceAuthorizationManager
。如果我在 中设置断点CustomAuthMgr.CheckAccess()
,那么在第一次调用时,operationContext.ServiceSecurityContext.AuthorizationContext
它是明确的,而在第二次调用时,它包含我在前一次调用中放入的任何内容。这是由 WCF 执行的我自己的代码的第一种方法,因此在授权阶段之前的某些东西正在重新加载我的AuthorizationContext
.
更新 3
一些补充信息:为了验证一些事情,我将我的服务实现更改为不再抛出异常,而是返回一个调用次数的计数器,加上当前线程 ID:
public string DoWork()
{
var o = System.ServiceModel.OperationContext.Current.ServiceSecurityContext.AuthorizationContext;
if (o != null)
{
if (o.Properties.ContainsKey("x"))
o.Properties["x"] = (int)o.Properties["x"] + 1;
else
o.Properties.Add("x", 1);
}
return o.Properties["x"].ToString() + " | " + System.AppDomain.GetCurrentThreadId().ToString();
}
然后从NUnit
GUI 运行一次测试会导致:
1 | 3816
然后我关闭NUnit
GUI,重新启动它,然后再次运行测试:
2 | 3816
然后我再次关闭NUnit
GUI,并TestDriven.NET
在 Visual Studio 中运行测试:
3 | 3816
所以它肯定会AuthorizationContext
在客户端进程之间保持我的状态,但是同一个线程处理每个服务调用,所以可能AuthorizationContext
只是线程静态或什么?
不,它与线程无关。我Thread.Sleep(10000);
在服务实现中添加了一个,然后NUnit
一次运行 2 个 GUI,每个都打印出“2”,但线程 ID 不同:
2 | 5500
2 | 5764
AuthorizationContext
跨线程也是如此。漂亮。