diff --git a/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java b/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java index a6b6a917..d19a59ac 100644 --- a/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java +++ b/security/src/main/java/org/springframework/ws/soap/security/AbstractWsSecurityInterceptor.java @@ -21,8 +21,14 @@ import javax.xml.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.util.Assert; +import org.springframework.ws.client.WebServiceClientException; +import org.springframework.ws.client.core.WebServiceTemplate; +import org.springframework.ws.client.support.interceptor.ClientInterceptor; import org.springframework.ws.context.MessageContext; +import org.springframework.ws.server.EndpointInterceptor; +import org.springframework.ws.server.endpoint.mapping.AbstractEndpointMapping; import org.springframework.ws.soap.SoapBody; import org.springframework.ws.soap.SoapFault; import org.springframework.ws.soap.SoapHeaderElement; @@ -31,46 +37,69 @@ import org.springframework.ws.soap.server.SoapEndpointInterceptor; import org.springframework.ws.soap.soap11.Soap11Body; /** - * Interceptor base class for interceptors that handle WS-Security. + * Interceptor base class for interceptors that handle WS-Security. Can be used on the server side, registered in a + * {@link AbstractEndpointMapping#setInterceptors(EndpointInterceptor[]) endpoint mapping}; or on the client side, on + * the {@link WebServiceTemplate#setInterceptors(ClientInterceptor[]) web service template}. *

- * Subclasses of this base class can be configured to validate incoming and secure outgoing messages. By default, both - * are on. + * Subclasses of this base class can be configured to secure incoming and secure outgoing messages. By default, both are + * on. * * @author Arjen Poutsma * @since 1.0.0 */ -public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInterceptor { +public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInterceptor, ClientInterceptor { + + /** Logger available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); private static final QName WS_SECURITY_NAME = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security"); - /** - * Logger available to subclasses. - */ - protected final Log logger = LogFactory.getLog(getClass()); - private boolean secureResponse = true; private boolean validateRequest = true; - /** - * Indicates whether outgoing responsed are to be secured. Defaults to true. - */ - public void setSecureResponse(boolean secureResponse) { - this.secureResponse = secureResponse; - } + private boolean secureRequest = true; - /** - * Indicates whether incoming request are to be validated. Defaults to true. - */ + private boolean validateResponse = true; + + /** Indicates whether server-side incoming request are to be validated. Defaults to true. */ public void setValidateRequest(boolean validateRequest) { this.validateRequest = validateRequest; } + /** Indicates whether server-side outgoing responses are to be secured. Defaults to true. */ + public void setSecureResponse(boolean secureResponse) { + this.secureResponse = secureResponse; + } + + /** Indicates whether client-side outgoing requests are to be secured. Defaults to true. */ + public void setSecureRequest(boolean secureRequest) { + this.secureRequest = secureRequest; + } + + /** Indicates whether client-side incoming responses are to be validated. Defaults to true. */ + public void setValidateResponse(boolean validateResponse) { + this.validateResponse = validateResponse; + } + + /* + * Server-side + */ + + /** + * Validates a server-side incoming request. Delegates to {@link #validateMessage(SoapMessage)} if the {@link + * #setValidateRequest(boolean) validateRequest} property is true. + * + * @param messageContext the message context, containing the request to be validated + * @param endpoint chosen endpoint to invoke + * @return true if the request was valid; false otherwise. + * @throws Exception in case of errors + * @see #validateMessage(SoapMessage) + */ public final boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { if (validateRequest) { - Assert.isTrue(messageContext.getRequest() instanceof SoapMessage, - "WsSecurityInterceptor requires a SoapMessage request"); + Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); try { validateMessage((SoapMessage) messageContext.getRequest()); return true; @@ -87,10 +116,20 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter } } + /** + * Secures a server-side outgoing response. Delegates to {@link #secureMessage(SoapMessage)} if the {@link + * #setSecureResponse(boolean) secureResponse} property is true. + * + * @param messageContext the message context, containing the response to be secured + * @param endpoint chosen endpoint to invoke + * @return true if the response was secured; false otherwise. + * @throws Exception in case of errors + * @see #secureMessage(SoapMessage) + */ public final boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { if (secureResponse) { - Assert.isTrue(messageContext.getResponse() instanceof SoapMessage, - "WsSecurityInterceptor requires a SoapMessage response"); + Assert.isTrue(messageContext.hasResponse(), "MessageContext contains no response"); + Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse()); try { secureMessage((SoapMessage) messageContext.getResponse()); return true; @@ -107,9 +146,7 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter } } - /** - * Returns true, i.e. faults are not secured. - */ + /** Returns true, i.e. fault responses are not secured. */ public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { return true; } @@ -118,6 +155,72 @@ public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInter return WS_SECURITY_NAME.equals(headerElement.getName()); } + /* + * Client-side + */ + + /** + * Secures a client-side outgoing request. Delegates to {@link #secureMessage(SoapMessage)} if the {@link + * #setSecureRequest(boolean) secureRequest} property is true. + * + * @param messageContext the message context, containing the request to be secured + * @return true if the response was secured; false otherwise. + * @throws Exception in case of errors + * @see #secureMessage(SoapMessage) + */ + public final boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { + if (secureRequest) { + Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest()); + try { + secureMessage((SoapMessage) messageContext.getRequest()); + return true; + } + catch (WsSecuritySecurementException ex) { + return handleSecurementException(ex, messageContext); + } + catch (WsSecurityFaultException ex) { + return handleFaultException(ex, messageContext); + } + } + else { + return true; + } + } + + /** + * Validates a client-side incoming response. Delegates to {@link #validateMessage(SoapMessage)} if the {@link + * #setValidateResponse(boolean) validateResponse} property is true. + * + * @param messageContext the message context, containing the response to be validated + * @return true if the request was valid; false otherwise. + * @throws Exception in case of errors + * @see #validateMessage(SoapMessage) + */ + public final boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { + if (validateResponse) { + Assert.isTrue(messageContext.hasResponse(), "MessageContext contains no response"); + Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse()); + try { + validateMessage((SoapMessage) messageContext.getResponse()); + return true; + } + catch (WsSecurityValidationException ex) { + return handleValidationException(ex, messageContext); + } + catch (WsSecurityFaultException ex) { + return handleFaultException(ex, messageContext); + } + } + else { + return true; + } + } + + /** Returns true, i.e. fault responses are not validated. */ + public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { + return true; + } + /** * Handles an securement exception. Default implementation logs the given exception, and returns * false. diff --git a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java index 016c6428..c9245b2f 100644 --- a/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java +++ b/security/src/test/java/org/springframework/ws/soap/security/xwss/XwsSecurityInterceptorTest.java @@ -20,6 +20,7 @@ import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPMessage; import junit.framework.TestCase; + import org.springframework.ws.context.DefaultMessageContext; import org.springframework.ws.context.MessageContext; import org.springframework.ws.soap.SoapMessage; @@ -35,7 +36,7 @@ public class XwsSecurityInterceptorTest extends TestCase { messageFactory = MessageFactory.newInstance(); } - public void testhandleRequest() throws Exception { + public void testhandleServerRequest() throws Exception { final SOAPMessage request = messageFactory.createMessage(); final SOAPMessage validatedRequest = messageFactory.createMessage(); XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { @@ -57,7 +58,7 @@ public class XwsSecurityInterceptorTest extends TestCase { assertEquals("Invalid request", validatedRequest, ((SaajSoapMessage) context.getRequest()).getSaajMessage()); } - public void testhandleResponse() throws Exception { + public void testhandleServerResponse() throws Exception { final SOAPMessage securedResponse = messageFactory.createMessage(); XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { @@ -74,8 +75,53 @@ public class XwsSecurityInterceptorTest extends TestCase { SOAPMessage request = messageFactory.createMessage(); MessageContext context = new DefaultMessageContext(new SaajSoapMessage(request), new SaajSoapMessageFactory(messageFactory)); + context.getResponse(); interceptor.handleResponse(context, null); assertEquals("Invalid response", securedResponse, ((SaajSoapMessage) context.getResponse()).getSaajMessage()); } + public void testhandleClientRequest() throws Exception { + final SOAPMessage request = messageFactory.createMessage(); + final SOAPMessage securedRequest = messageFactory.createMessage(); + XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { + + protected void secureMessage(SoapMessage soapMessage) throws XwsSecuritySecurementException { + SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage; + assertEquals("Invalid message", request, saajSoapMessage.getSaajMessage()); + saajSoapMessage.setSaajMessage(securedRequest); + } + + protected void validateMessage(SoapMessage message) throws WsSecurityValidationException { + fail("validate not expected"); + } + + }; + MessageContext context = + new DefaultMessageContext(new SaajSoapMessage(request), new SaajSoapMessageFactory(messageFactory)); + interceptor.handleRequest(context); + assertEquals("Invalid request", securedRequest, ((SaajSoapMessage) context.getRequest()).getSaajMessage()); + } + + public void testhandleClientResponse() throws Exception { + final SOAPMessage validatedResponse = messageFactory.createMessage(); + XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor() { + + protected void secureMessage(SoapMessage message) throws XwsSecuritySecurementException { + fail("secure not expected"); + } + + protected void validateMessage(SoapMessage soapMessage) throws WsSecurityValidationException { + SaajSoapMessage saajSoapMessage = (SaajSoapMessage) soapMessage; + saajSoapMessage.setSaajMessage(validatedResponse); + } + + }; + SOAPMessage request = messageFactory.createMessage(); + MessageContext context = + new DefaultMessageContext(new SaajSoapMessage(request), new SaajSoapMessageFactory(messageFactory)); + context.getResponse(); + interceptor.handleResponse(context); + assertEquals("Invalid response", validatedResponse, ((SaajSoapMessage) context.getResponse()).getSaajMessage()); + } + } \ No newline at end of file