Reputation: 43718
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:
TestBusinessClient
is created.DoWork()
.DoWork()
does not find "X" in the AuthorizationContext.Properties
.DoWork()
adds "X" to the AuthorizationContext.Properties
.TestBusinessClient
is created.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:
DoWork()
.TestDriven.NET
within VS 2008..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
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
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
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
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