5

我正在做一些单元测试,并在 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();
        }
    }

因此,浏览运行时发生的情况:

  1. 一个新TestBusinessClient的被创建。
  2. 它调用DoWork().
  3. DoWork()在 中找不到“X” AuthorizationContext.Properties
  4. DoWork()将“X”添加到AuthorizationContext.Properties.
  5. 测试方法处理第一个客户端。
  6. 新的第二个TestBusinessClient被创建。
  7. 它调用DoWork().
  8. DoWork() 确实在上次调用的属性中找到“X”。
  9. DoWork()抛出异常。

所以我的问题是;当新客户端连接到同一服务时,为什么OperationContextandAuthorizationContext没有被杀死?我确实理解wsHttpBinding默认情况下在调用之间使用会话上下文,但我认为会话将​​是每个客户端的。当我连接到客户端的新实例时,我希望 WCF 会话及其上下文全部更新。

任何人有任何想法或想法?此处所需的结果是在两个单独的客户端AuthorizationContext.Properties的两次调用之间重置。DoWork()


更新 1

我尝试设置服务PerCallPerSession,但都没有改变。

我还尝试了开启和关闭可靠消息传递的服务,但都没有改变任何东西。

我还在第一次调用和第二次调用时保存了我的 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 "";
}

作为健全性检查,我做了以下工作:

  1. 启动 WCF 服务器的一个实例(使用 Cassini/集成 VS 2008 服务器)。
  2. 将测试夹具减少到只有 1 次调用DoWork().
  3. TestDriven.NET在 VS 2008 中运行测试。
  4. .dllNUnit'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();
    }

然后从NUnitGUI 运行一次测试会导致:

1 | 3816

然后我关闭NUnitGUI,重新启动它,然后再次运行测试:

2 | 3816

然后我再次关闭NUnitGUI,并TestDriven.NET在 Visual Studio 中运行测试:

3 | 3816

所以它肯定会AuthorizationContext在客户端进程之间保持我的状态,但是同一个线程处理每个服务调用,所以可能AuthorizationContext只是线程静态或什么?

不,它与线程无关。我Thread.Sleep(10000);在服务实现中添加了一个,然后NUnit一次运行 2 个 GUI,每个都打印出“2”,但线程 ID 不同:

2 | 5500
2 | 5764

AuthorizationContext跨线程也是如此。漂亮。

4

4 回答 4

1

这取决于您的服务配置,您为 SessionMode 选择的值:

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required)]
public interface ICalculatorSession

http://msdn.microsoft.com/en-us/library/ms733040.aspx

http://msdn.microsoft.com/en-us/library/system.servicemodel.sessionmode.aspx

于 2009-07-27T18:57:39.863 回答
1

这实际上取决于您如何定义会话和客户端。如果您从同一个进程进行两次调用,则 WCF 通信层很有可能将它们多路复用到同一个连接。

您是否真的尝试过从完全不同的进程运行两个连接?你能达到同样的结果吗?

于 2009-07-27T18:39:13.490 回答
1

WsHttp 绑定将默认在会话模式下运行,因为默认情况下已打开安全性。如果您完全关闭它(如您的配置所示),它将恢复为每次调用模式,除非您的服务合同特别需要会话。

只要没有发生任何会话超时,会话就会持续 - 您可以在服务器上定义一个,在客户端定义另一个,较短的一个“获胜”。指定它的最简单方法是向 wsHttpBinding 添加可靠消息传递:

  <wsHttpBinding>
    <binding>
      <reliableSession inactivityTimeout="00:10:00"/>
    </binding>
  </wsHttpBinding>

否则,您必须在代码中或通过定义自定义绑定来完成。

或者:您还可以在服务合同上定义某些方法作为“终止”方法,如果这些方法已被调用并终止,则会话也将被销毁。

[ServiceContract(SessionMode=SessionMode.Required)]
interface YourServiceContract
{
    [OperationContract(IsInitiating = true)]
    void InitMethod();

    [OperationContract]
    void WorkMethod()

    [OperationContract(IsTerminating=true)]
    void EndSessionMethod()
}

在这里,调用EndSessionMethod将终止会话,并且不能再调用特定代理实例上的服务。

或者:服务器端未处理的异常未捕获并变成 FaultException 也会导致通道出错并中止任何可能正在进行的会话。

但正如您所指出的 - 我也希望在客户端代理被处理时丢弃整个会话和所有相关数据,并为后续调用重新创建。我对你的发现有点惊讶......

马克

于 2009-07-27T19:09:49.527 回答
0

我有类似的问题 - 我看到每次通话后AuthorizationContext都保持不变。

我注意到在你的配置文件中你有

<serviceAuthorization principalPermissionMode="Custom" ....

尝试删除principalPermissionMode="Custom"。至少在我的情况下,每个新调用都带有 null AuthorizationContext。

于 2017-07-28T18:38:38.887 回答