Re: friendly error messages for usernameForCertificateSecurity
- From: "Philip Ross" <philip.ross@xxxxxxxxxxxxxxxxxx>
- Date: Wed, 12 Jul 2006 16:04:39 +0100
I have been experimenting recently with possible workarounds to this same
problem.
As far as I can determine, the
CustomeUsernameTokenManager.AuthenticateToken() method is called as part of
the receive security filter processing to create the
Microsoft.Web.Services3.Security.Security object that is passed to the
current policy assertion's ValidateMessageSecurity() method. Only if the
message security is valid is it put into the OperationState for the current
context. The consequence is that if you cause AuthenticateToken() to fail by
throwing an exception or returning a null response then the Security
instance isn't stored in the OperationState and is therefore unavailable to
the service's SendSecurityFilter, and thus the missing security header in
the response received at the client.
To work around this behaviour I created a
CustomUsernameForCertificateAssertion that subclasses the
Microsoft.Web.Services3.Design.UsernameForCertificateAssertion class. The
basic idea then is to not throw an error from AuthenticateToken() but set
the token.Principal to have an Identity whose property IsAuthenticated
returns false. This is tested in an overloaded ValidateMessageSecurity()
method in my custom assertion's service input filter and then throw an error
if not valid. This error is then returned in a properly formatted Response
message with a valid security header and appears as a SoapException with som
well known message text.
Misconfigurations could lead to unexpected security exposures, for example
having a custom username token manager that always succeeds but forgetting
to match to a custom username for certificate assertion implementation. I
minimised the exposure by placing a marker interface on my policy assertion
and checking for it in a new base for my custom username manager. To
illustrate this, here are some code snippits:
hope this helps your thought processes and experiments,
--p.
// CustomUsernameForCertificateAssertion that checks IsAuthenticated on
Identity
public class CustomUsernameForCertificateAssertion :
UsernameForCertificateAssertion, IDeferredAuthenticateToken
{
protected class MyServiceInputFilter : ServiceInputFilter
{
#region Constructor and private fields ...
public override SoapFilterResult ProcessMessage( SoapEnvelope
envelope )
{
// used in custom token manager base
envelope.Context.MessageState.Set<PolicyAssertion>(
_assertion );
return base.ProcessMessage( envelope );
}
public override void ValidateMessageSecurity( SoapEnvelope envelope,
Security security, MessageProtectionRequirements request )
{
base.ValidateMessageSecurity( envelope, security, request );
SecurityToken token = envelope.Context.IdentityToken;
if ( !token.Identity.IsAuthenticated )
{
throw new throw new SoapException( "Authentication
error", SoapException.ServerFaultCode );
}
}
}
#region Rest of class ...
}
// Base for custom token managers that always succeed
public abstract class UsernameTokenManagerBase : UsernameTokenManager
{
protected sealed override string AuthenticateToken( UsernameToken
token )
{
string password = AuthenticateTokenCore( token );
GenericIdentity identity = null;
GenericPrincipal principal = null;
// if failed, throw exception unless assertion implements
IDeferredAuthenticateToken.
if ( string.IsNullOrEmpty( password ) )
{
SoapContext context = SoapContext.Current;
PolicyAssertion assertion =
context.MessageState.Get<PolicyAssertion>();
if ( assertion != null && ( assertion is
IDeferredAuthenticateToken ) )
{
identity = new GenericIdentity( string.Empty );
principal = new GenericPrincipal( identity, null );
}
else
{
throw new ApplicationException( "The user couldn't be
authenticated" );
}
}
else
{
identity = new GenericIdentity( token.Username );
principal = new GenericPrincipal( identity, null );
}
token.Principal = principal;
return token.Password;
}
// new method a custom token manager must implement
protected abstract string AuthenticateTokenCore( UsernameToken
token );
}
// custom token manager, this one happens to validate against a Membership
DB.
public class CustomUsernameTokenManager : UsernameTokenManagerBase
{
protected override string AuthenticateTokenCore ( UsernameToken
token )
{
bool validCredentials = Membership.ValidateUser( token.Username,
token.Password );
// if failed, return null.
if ( !validCredentials )
{
return null;
}
GenericIdentity identity = new GenericIdentity(
token.Username );
GenericPrincipal principal = new GenericPrincipal( identity,
null );
token.Principal = principal;
return token.Password;
}
}
In the Web.Config in the security section of <microsoft.web.services3> add
something similar to:
<securityTokenManager>
<add localName="UsernameToken"
namespace="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" type="CustomTokenManagers.CustomUsernameTokenManager, CustomTokenManagers"/></securityTokenManager>and in your policy file add an extension element: <extensions> <extension name="usernameForCertificateSecurity" type="CustomTokenManagers.CustomUsernameForCertificateAssertion,CustomTokenManagers"/> </extensions>"geobier" <geobier@xxxxxxxxxxxxxxxxxxxxxxxxx> wrote in messagenews:D9CB7856-822A-457E-8993-59153D9E4076@xxxxxxxxxxxxxxxx> This has been asked several times on the web, but I have yet to see asolution.>> In short:>> Using usernameForCertificateSecurity, with a custom UsenameTokenManager,> AuthenticateToken detects an error ( "Invalid user id or password","account> is locked", "account expires in 5 days"). How can this error message be> returned to the client in a simple way.>> More specifically:>> protected override string AuthenticateToken(UsernameToken token)> {> ....>> // password is invalid!!!!> // What is the code to put here?????> // throw new SoapException("Bad juju", new XmlQualifiedName("geb:hi"));> }>> In the code above if I comment out the throwing of the soap exception onthe> client side, I receive back the dreaded:>> "WSE910: An error happened during the processing of a response message,and> you can find the error in the inner exception.">> the inner exception is equally useless:>> "Security requirements are not satisfied because the security header isnot> present in the incoming message.">> If you recast the outer exception as follows:>> catch (Exception exc)> {> Microsoft.Web.Services3.ResponseProcessingException temp => (Microsoft.Web.Services3.ResponseProcessingException) exc;> string message = temp.Response.Fault.Message;> }>> you CAN get to the original message, ("bad juju") just follow the --->three> deep:>> message:>> System.Web.Services.Protocols.SoapHeaderException: Server unavailable,> please try later ---> System.ApplicationException: WSE841: An erroroccured> processing an outgoing fault response. --->> System.Web.Services.Protocols.SoapException:> System.Web.Services.Protocols.SoapException: Bad juju\r\n at> Ingenix.Omx.WS.UTM.AuthenticateToken(UsernameToken token) in> C:\\WebSites\\HelloWSE\\TokenManager\\TokenManager.cs:line 200\r\n at>Microsoft.Web.Services3.Security.Tokens.UsernameTokenManager.VerifyToken(SecurityToken> token)\r\n at Ingenix.Omx.WS.UTM.VerifyToken(SecurityToken token) in> C:\\WebSites\\HelloWSE\\TokenManager\\TokenManager.cs:line 169\r\n at>Microsoft.Web.Services3.Security.Tokens.SecurityTokenManager.LoadXmlSecurityToken(XmlElement> element)\r\n at>Microsoft.Web.Services3.Security.Tokens.SecurityTokenManager.GetTokenFromXml(XmlElement> element)\r\n at> Microsoft.Web.Services3.Security.Security.LoadToken(XmlElement element,> SecurityConfiguration configuration, Int32& tokenCount)\r\n at> Microsoft.Web.Services3.Security.Security.LoadXml(XmlElement element)\r\n> at Microsoft.Web.Services3.Security.Security.CreateFrom(SoapEnvelope> envelope, String localActor, String serviceActor)\r\n at>Microsoft.Web.Services3.Security.ReceiveSecurityFilter.ProcessMessage(SoapEnvelope> envelope)\r\n at> Microsoft.Web.Services3.Pipeline.ProcessInputMessage(SoapEnvelope> envelope)\r\n at> Microsoft.Web.Services3.WseProtocol.FilterRequest(SoapEnvelope> requestEnvelope)\r\n at> Microsoft.Web.Services3.WseProtocol.RouteRequest(SoapServerMessage> message)\r\n at> System.Web.Services.Protocols.SoapServerProtocol.Initialize()\r\n at> System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type,> HttpContext context, HttpRequest request, HttpResponse response, Boolean&> abortProcessing)\r\n --- End of inner exception stackace ---\r\n ---> End of inner exception stack trace ---" string>> Trying to tell my clients that they need to parse this message to get the> true error message is a less then desireable solution.>> Questions:>> 1) Is there some way to have the exception show up in the InnerMessage> exception tree? Telling my clients to follow the inner exception tree isa> much better solution.>> 2) Is there an alternative way of communicating the error information back> rather then throwing an exception. The above example uses aSoapException,> if you change it to thrown new Exception("bad juju"), the results are> similar. Anyone got a better solution?>> Restrictions:>> Custom assertion policy on the client side is not going to be anacceptable> solution with my clients. We are already pushing the limits on client> configuration complexity using the canned policies.>> On the server side, which I control, I am willing to code as complicated a> solution as required to be able to return to my clients a reasonablemessage.>> Observation:>> This seems like a common use case for this security case. All the> quickstarts and examples I was able to track down skip this issue. Ionly> found one client side example that mentioned this case and it simplyshowed> the client catching the exception, without actually going into what the> exception contained. Is anyone actually really using this stuff (sigh)?As> I said, other people have asked this question before.>> Alternative:>> One thought I have been playing with is always accepting the credential,but> setting a flag in the UsernameToken indicating that the user did not> validate, with a reason code. My server side protected webmethods wouldthen> do a validation check at entry, and at that point I could return areasonable> error message or SoapException. This is one heck of an ugly workaround,but> gets me out of this mess. Opinions?>> thanks,> --george>>>>>>>> --> thanks,> --george
.
- References:
- Prev by Date: Re: Separating Schema and Services during versioning
- Next by Date: Re: Separating Schema and Services during versioning
- Previous by thread: Re: friendly error messages for usernameForCertificateSecurity
- Next by thread: Re: Separating Schema and Services during versioning
- Index(es):
Relevant Pages
|
Loading