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:
- A new TestBusinessClient is created.
- It makes a call to DoWork().
- DoWork() does not find "X" in the AuthorizationContext.Properties.
- DoWork() adds "X" to the AuthorizationContext.Properties.
- The test method disposes of the first client.
- A new second TestBusinessClient is created.
- It makes a call to DoWork().
- DoWork() does find "X" still in the properties from the last call.
- 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,a nd 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:
- Start an instance of the wcf server (using Cassini / integrated VS 2008 server).
- Reduce teh test fixture to only 1 call to DoWork().
- Run the test from TestDriven.NET winthin VS 2008.
- 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 ont he second call, it contains whatever I put in it from the previous call. This is the 1st 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 # 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,a nd run the test from TestDriven.NET within Visual Studio: 3 | 3816
So its definately 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.