Intercepting WCF Operation Calls with Impersonated Identity

Take the following scenario. You have a WCF webservice with several operations. The communication runs within a secure SSL channel and uses Kerberos authentication. The webservice application runs with an assigned application pool user, in your .Net code represented by the WindowsIdentity.

The requirement is to make a call to another webservice - let's call it the authorization service - which takes the currently authenticated Identity and performs some further business logic in order to determine whether the user has the right to access given resources.
What comes immediately to mind is that we need to impersonate the caller in order to have the right identity sent to the authorization service. Otherwise we'd get the application pool user identity transferred which would be nonsense. Impersonation on WCF operations turns out to be quite straightforward. All you need is to annotate your operation with the right attribute
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public string Hello(string message)
{
//execute the call to the authorization service

//if the current identity is authorized execute the custom
//business logic

return "hello";
}
This would work. I don't like this approach however. The problem is that most of the webservices would need to first check the user authorization by calling the authorization service. So every single programmer would need to implement the call which is not an acceptable, clean solution and way too much error prone.

Solution approach 1: Implement a custom WCF authorization policy

WCF allows you to specify so-called authorization policies. All you need to do is to implement the IAuthorization interface appropriately.
public class MyCustomAuthorizationPolicy : IAuthorizationPolicy
{

public boolean Evaluate(EvaluationContext context)
{
evaluationContext.Properties["Principal"] = //call the authorization service to retrieve an appropriate principal
...
}
}
In the service's configuration you need to register your policy

This is a nice mechanism because it allows you to decouple the authorization logic (i.e. the call to the authorization service) from the real business logic. Your webservice operation doesn't even get called.
The problem with this approach however is that I was not able to impersonate the caller at this level.

Solution approach 2: Parameter Inspectors

Not succeeding with custom authorization policies I tried to find an alternative to intercept the WS call in order to to my authorization logic. Parameter inspectors seemed to be suitable at first glance. Their main purpose is to do parameter validation.
public void AfterCall(string operationName, object[] outputs, object returnValue, object correlationState)
{ }

public object BeforeCall(string operationName, object[] inputs)
{ }
The method names are quite self-explanatory. However, you already guessed it, impersonation didn't work at this level neither.

Final approach and solution

My last hope was to rely on OperatonInvokers. They are the last one to be called in the chain of calls (see below) that are executed when a webservice operaton gets invoked.
  1. Message Inspection
  2. Operation Selector
  3. Message Formatting
  4. Parameter Inspection
  5. Operation Invoker
The main methods of interest in the IOperationInvoker interface are the Invoke(...), BeginInvoke(...) and EndInvoke(...)

Invoke - Returns an object and a set of output objects from an instance and set of input objects.
BeginInvoke - An asynchronous implementation of the Invoke method.
EndInvoke - The asynchronous end method.

So, my custom operation invoker implementation turned out to be as follows
class AuthenticationOperationInvoker : IOperationInvoker
{
private IOperationInvoker defaultInvoker;

public AuthenticationOperationInvoker(IOperationInvoker defaultInvoker)
{
this.defaultInvoker = defaultInvoker;
}

public object[] AllocateInputs()
{
return defaultInvoker.AllocateInputs();
}

public object Invoke(object instance, object[] inputs, out object[] outputs)
{
//execute the custom authorization logic and set the thread principal accordingly

return defaultInvoker.Invoke(instance, inputs, out outputs);
}

public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
return defaultInvoker.InvokeBegin(instance, inputs, callback, state);
}

public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
return defaultInvoker.InvokeEnd(instance, out outputs, result);
}

public bool IsSynchronous
{
get { return defaultInvoker.IsSynchronous; }
}
}
Note, my custom OperationInvoker is mainly a wrapper, taking the default operation invoker in the constructor and delegating all operations to it in order to not break anything existing. In line 17, within the Invoke method I do inject my custom authorization logic and yes, here impersonation works.

To make this whole thing usable I wrap everything in a custom "authorization attribute" which implements the IOperationBehavior interface.
public class AuthorizedMethodAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{ }

public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation)
{ }

public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
{
IOperationInvoker defaultInvoker = dispatchOperation.Invoker;
dispatchOperation.Invoker = new AuthenticationOperationInvoker(defaultInvoker);
}

public void Validate(OperationDescription operationDescription)
{}
}
In line 11 and 12 I fetch the default operation invoker and inject it into my custom which then replaces the operation invoker used by my webservice.

That's it, now developers can just implement their webservices just as normal and add my custom attribute in order to augment the authorization process by calling the authorization service.
[AuthorizedMethod]
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public string Hello(string message)
{
//execute the custom webservice's business logic

return "hello";
}
Kindle

Comments

0

Your ad here?