CodingWithSpike
CodingWithSpike

Reputation: 43718

WCF AuthorizationContext not cleared between calls from different clients. (was: When does a WCF service session actually end?)

I was doing some unit testing, and ran into an interesting problem in WCF. I have a service using the wsHttpBinding, configured as such:

<bindings>
  <wsHttpBinding>
    <binding name="wsHttpUnsecured">
      <security mode="None">
        <transport clientCredentialType="None" />
        <message clientCredentialType="None" />
      </security>
    </binding>
  </wsHttpBinding>

The implementation of the service is:

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");
}

All it is doing here is checking the operation context, and if there is not an "X" in the AuthorizationContext, then add one. If there was already an "X", then exception out. (this is set up strictly as a simple test. in normal use, this would be happening in a custom AuthorizationManager and Token Authenticator).

So basically, if we call the method more than once within the same operationContext and AuthorizationContext, then we will see an exception.

Now, here is my test fixture.

    [Test]
    public void CallTwice()
    {
        using (var cli1 = new TestBusinessClient())
        {
            cli1.DoWork();
            cli1.Close();
        }
        using (var cli2 = new TestBusinessClient())
        {
            cli2.DoWork();
            cli2.Close();
        }
    }

So walking through what happens at runtime:

  1. A new TestBusinessClient is created.
  2. It makes a call to DoWork().
  3. DoWork() does not find "X" in the AuthorizationContext.Properties.
  4. DoWork() adds "X" to the AuthorizationContext.Properties.
  5. The test method disposes of the first client.
  6. A new second TestBusinessClient is created.
  7. It makes a call to DoWork().
  8. DoWork() does find "X" still in the properties from the last call.
  9. DoWork() throws an exception.

So my question is; why is the OperationContext and AuthorizationContext not killed off when a new client connects to the same service? I do understand that wsHttpBinding by default uses a session context between the calls, but I would think that session would be per client. I expected the WCF session, and therefore its contexts, to all renew when I connect with a new instance of a client.

Anyone have any thoughts or ideas? The desired result here is for AuthorizationContext.Properties to be reset between the two calls to DoWork() by the two separate clients.


Update 1

I tried setting the service PerCall and PerSession, and neither made a difference.

I also tried the service with reliable messaging on and off, and neither changed anything.

I also saved off my operationContext at the first call and the second call, and compared the two:

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

So it kind of looks like the operation context is changed / recreated, but something is setting the same AuthorizationContext on each subsequent service call.


Update 2

Here is all the server-side stuff:

[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 "";
}

As a sanity check, I did the following:

  1. Start an instance of the WCF server (using Cassini / integrated VS 2008 server).
  2. Reduce the test fixture to only 1 call to DoWork().
  3. Run the test from TestDriven.NET within VS 2008.
  4. Open the same test .dll from NUnit's standalone GUI tool, and run it.

The first test run passes, and the second fails. So it seems this is purely server side, as running the same service call from 2 different processes ends up with the same AuthorizationContext.

I'm starting to wonder if maybe something internal to WCF is still stuck on WindowsAuthentication and reusing the same Auth Context since I'm logged into the same domain with the same user name? My service is set to use a custom AuthorizationManager:

        <serviceBehaviors>
            <behavior name="myBehavior">
                <serviceMetadata httpGetEnabled="false"  />
                <serviceDebug includeExceptionDetailInFaults="true" />
                <serviceAuthorization principalPermissionMode="Custom" serviceAuthorizationManagerType="My.Namespace.CustomAuthMgr" />
            </behavior>

Where My.Namespace.CustomAuthMgr extends ServiceAuthorizationManager. If I set a breakpoint in CustomAuthMgr.CheckAccess(), then on the first call, operationContext.ServiceSecurityContext.AuthorizationContext is clear, and on the second call, it contains whatever I put in it from the previous call. This is the first method of my own code that is executed by WCF, so something before the Authorization phase is reloading my AuthorizationContext.


Update 3

Some added info: In order to validate some things, I changes my service implementation to no longer throw an exception, and instead return a counter of the number of times called, plus the current thread 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();
    }

Then running the test once from NUnit GUI results in:

1 | 3816

I then close the NUnit GUI, restart it, and run the test again:

2 | 3816

I then close the NUnit GUI again, and run the test from TestDriven.NET within Visual Studio:

3 | 3816

So its definitely persisting my AuthorizationContext between client processes, but the same thread handles each service call, so maybe AuthorizationContext is just Thread-static or something?

Nope, it has nothing to do with the thread. I added a Thread.Sleep(10000); to the service implementation, then ran 2 NUnit GUIs at once, and each one printed out "2" but with different thread IDs:

2 | 5500
2 | 5764

So AuthorizationContext is being persisted across threads too. Nifty.

Upvotes: 5

Views: 2637

Answers (4)

Vitaliy Markitanov
Vitaliy Markitanov

Reputation: 2448

I have similar issue - I see that AuthorizationContext stays the same after each call.

I've noticed that in your config file you have

<serviceAuthorization principalPermissionMode="Custom" ....

Try to remove principalPermissionMode="Custom". At least in my case every new call comes with null AuthorizationContext.

Upvotes: 0

marc_s
marc_s

Reputation: 754508

WsHttp binding will by default run in session mode, since security is turned on by default. If you turn it off completely (as your config seems to show), it'll revert to per-call mode, unless your service contract specifically requires a session.

The session will last as long as none of the session timeouts have occured - you can define one on the server, and another one on the client, and the shorter one "wins". The easiest way to specify it is by adding the reliable messaging to the wsHttpBinding:

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

Otherwise you'll have to do it in code or by defining your custom binding.

OR: you can also define certain methods on your service contract to be "terminating" methods, and if those have been called and terminate, the session will be destroyed as well.

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

    [OperationContract]
    void WorkMethod()

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

Here, calling the EndSessionMethod will terminate the session and no more calls can be made to the service on the particular proxy instance.

OR: an unhandled exception on the server side which is not caught and turned into a FaultException will also fault the channel and abort any session that might have been ongoing.

But as you point out - I, too, would have expected the entire session and all associated data to be discarded when the client proxy is disposed of, and recreated for a subsequent call. I'm a bit surprised by your findings....

Marc

Upvotes: 1

Shiraz Bhaiji
Shiraz Bhaiji

Reputation: 65391

It depends on the configuration of your service, what value you choose for your 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

Upvotes: 1

Christopher
Christopher

Reputation: 9084

That really depends on how you are defining session and client. If you are making two calls from the same process, there is every possibility that the WCF communication layer is multiplexing them down the same connection.

Have you actually tried to run two connects from entirely different processes? Do you achieve the same results?

Upvotes: 1

Related Questions