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 theAuthorizationContext.Properties
.DoWork()
adds "X" to theAuthorizationContext.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, 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:
- Start an instance of the WCF server (using Cassini / integrated VS 2008 server).
- Reduce the test fixture to only 1 call to
DoWork()
. - Run the test from
TestDriven.NET
within VS 2008. - Open the same test
.dll
fromNUnit'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.