View Javadoc

1   /*
2    * Copyright 2002-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.soap.security;
18  
19  import java.util.Locale;
20  import javax.xml.namespace.QName;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  import org.springframework.util.Assert;
26  import org.springframework.ws.client.WebServiceClientException;
27  import org.springframework.ws.client.support.interceptor.ClientInterceptor;
28  import org.springframework.ws.context.MessageContext;
29  import org.springframework.ws.server.EndpointExceptionResolver;
30  import org.springframework.ws.soap.SoapBody;
31  import org.springframework.ws.soap.SoapFault;
32  import org.springframework.ws.soap.SoapHeaderElement;
33  import org.springframework.ws.soap.SoapMessage;
34  import org.springframework.ws.soap.server.SoapEndpointInterceptor;
35  import org.springframework.ws.soap.soap11.Soap11Body;
36  
37  /**
38   * Interceptor base class for interceptors that handle WS-Security. Can be used on the server side, registered in a
39   * {@link org.springframework.ws.server.endpoint.mapping.AbstractEndpointMapping#setInterceptors(org.springframework.ws.server.EndpointInterceptor[])
40   * endpoint mapping}; or on the client side, on the {@link org.springframework.ws.client.core.WebServiceTemplate#setInterceptors(ClientInterceptor[])
41   * web service template}.
42   * <p/>
43   * Subclasses of this base class can be configured to secure incoming and secure outgoing messages. By default, both are
44   * on.
45   *
46   * @author Arjen Poutsma
47   * @since 1.0.0
48   */
49  public abstract class AbstractWsSecurityInterceptor implements SoapEndpointInterceptor, ClientInterceptor {
50  
51      /** Logger available to subclasses. */
52      protected final Log logger = LogFactory.getLog(getClass());
53  
54      protected static final QName WS_SECURITY_NAME =
55              new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security");
56  
57      private boolean secureResponse = true;
58  
59      private boolean validateRequest = true;
60  
61      private boolean secureRequest = true;
62  
63      private boolean validateResponse = true;
64  
65      private EndpointExceptionResolver exceptionResolver;
66  
67      /** Indicates whether server-side incoming request are to be validated. Defaults to <code>true</code>. */
68      public void setValidateRequest(boolean validateRequest) {
69          this.validateRequest = validateRequest;
70      }
71  
72      /** Indicates whether server-side outgoing responses are to be secured. Defaults to <code>true</code>. */
73      public void setSecureResponse(boolean secureResponse) {
74          this.secureResponse = secureResponse;
75      }
76  
77      /** Indicates whether client-side outgoing requests are to be secured. Defaults to <code>true</code>. */
78      public void setSecureRequest(boolean secureRequest) {
79          this.secureRequest = secureRequest;
80      }
81  
82      /** Indicates whether client-side incoming responses are to be validated. Defaults to <code>true</code>. */
83      public void setValidateResponse(boolean validateResponse) {
84          this.validateResponse = validateResponse;
85      }
86  
87      /** Provide an {@link EndpointExceptionResolver} for resolving validation exceptions. */
88      public void setExceptionResolver(EndpointExceptionResolver exceptionResolver) {
89          this.exceptionResolver = exceptionResolver;
90      }
91  
92      /*
93       * Server-side
94       */
95  
96      /**
97       * Validates a server-side incoming request. Delegates to {@link #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
98       * if the {@link #setValidateRequest(boolean) validateRequest} property is <code>true</code>.
99       *
100      * @param messageContext the message context, containing the request to be validated
101      * @param endpoint       chosen endpoint to invoke
102      * @return <code>true</code> if the request was valid; <code>false</code> otherwise.
103      * @throws Exception in case of errors
104      * @see #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)
105      */
106     public final boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
107         if (validateRequest) {
108             Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest());
109             try {
110                 validateMessage((SoapMessage) messageContext.getRequest(), messageContext);
111                 return true;
112             }
113             catch (WsSecurityValidationException ex) {
114                 return handleValidationException(ex, messageContext);
115             }
116             catch (WsSecurityFaultException ex) {
117                 return handleFaultException(ex, messageContext);
118             }
119         }
120         else {
121             return true;
122         }
123     }
124 
125     /**
126      * Secures a server-side outgoing response. Delegates to {@link #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
127      * if the {@link #setSecureResponse(boolean) secureResponse} property is <code>true</code>.
128      *
129      * @param messageContext the message context, containing the response to be secured
130      * @param endpoint       chosen endpoint to invoke
131      * @return <code>true</code> if the response was secured; <code>false</code> otherwise.
132      * @throws Exception in case of errors
133      * @see #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)
134      */
135     public final boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
136         boolean result = true;
137         try {
138             if (secureResponse) {
139                 Assert.isTrue(messageContext.hasResponse(), "MessageContext contains no response");
140                 Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse());
141                 try {
142                     secureMessage((SoapMessage) messageContext.getResponse(), messageContext);
143                 }
144                 catch (WsSecuritySecurementException ex) {
145                     result = handleSecurementException(ex, messageContext);
146                 }
147                 catch (WsSecurityFaultException ex) {
148                     result = handleFaultException(ex, messageContext);
149                 }
150             }
151         }
152         finally {
153             if (!result) {
154                 messageContext.clearResponse();
155             }
156             cleanUp();
157         }
158         return result;
159     }
160 
161     /** Returns <code>true</code>, i.e. fault responses are not secured. */
162     public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
163         cleanUp();
164         return true;
165     }
166 
167     public boolean understands(SoapHeaderElement headerElement) {
168         return WS_SECURITY_NAME.equals(headerElement.getName());
169     }
170 
171     /*
172      * Client-side
173      */
174 
175     /**
176      * Secures a client-side outgoing request. Delegates to {@link #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
177      * if the {@link #setSecureRequest(boolean) secureRequest} property is <code>true</code>.
178      *
179      * @param messageContext the message context, containing the request to be secured
180      * @return <code>true</code> if the response was secured; <code>false</code> otherwise.
181      * @throws Exception in case of errors
182      * @see #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)
183      */
184     public final boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
185         if (secureRequest) {
186             Assert.isInstanceOf(SoapMessage.class, messageContext.getRequest());
187             try {
188                 secureMessage((SoapMessage) messageContext.getRequest(), messageContext);
189                 return true;
190             }
191             catch (WsSecuritySecurementException ex) {
192                 return handleSecurementException(ex, messageContext);
193             }
194             catch (WsSecurityFaultException ex) {
195                 return handleFaultException(ex, messageContext);
196             }
197         }
198         else {
199             return true;
200         }
201     }
202 
203     /**
204      * Validates a client-side incoming response. Delegates to {@link #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
205      * if the {@link #setValidateResponse(boolean) validateResponse} property is <code>true</code>.
206      *
207      * @param messageContext the message context, containing the response to be validated
208      * @return <code>true</code> if the request was valid; <code>false</code> otherwise.
209      * @throws Exception in case of errors
210      * @see #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)
211      */
212     public final boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
213         if (validateResponse) {
214             Assert.isTrue(messageContext.hasResponse(), "MessageContext contains no response");
215             Assert.isInstanceOf(SoapMessage.class, messageContext.getResponse());
216             try {
217                 validateMessage((SoapMessage) messageContext.getResponse(), messageContext);
218                 return true;
219             }
220             catch (WsSecurityValidationException ex) {
221                 return handleValidationException(ex, messageContext);
222             }
223             catch (WsSecurityFaultException ex) {
224                 return handleFaultException(ex, messageContext);
225             }
226         }
227         else {
228             return true;
229         }
230     }
231 
232     /** Returns <code>true</code>, i.e. fault responses are not validated. */
233     public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
234         return true;
235     }
236 
237     /**
238      * Handles an securement exception. Default implementation logs the given exception, and returns
239      * <code>false</code>.
240      *
241      * @param ex             the validation exception
242      * @param messageContext the message context
243      * @return <code>true</code> to continue processing the message, <code>false</code> (the default) otherwise
244      */
245     protected boolean handleSecurementException(WsSecuritySecurementException ex, MessageContext messageContext) {
246         if (logger.isErrorEnabled()) {
247             logger.error("Could not secure response: " + ex.getMessage(), ex);
248         }
249         return false;
250     }
251 
252     /**
253      * Handles an invalid SOAP message. Default implementation logs the given exception, delegates to the set {@link
254      * #setExceptionResolver(EndpointExceptionResolver) exceptionResolver} if any, or creates a SOAP 1.1 Client or SOAP
255      * 1.2 Sender Fault with the exception message as fault string, and returns <code>false</code>.
256      *
257      * @param ex             the validation exception
258      * @param messageContext the message context
259      * @return <code>true</code> to continue processing the message, <code>false</code> (the default) otherwise
260      */
261     protected boolean handleValidationException(WsSecurityValidationException ex, MessageContext messageContext) {
262         if (logger.isWarnEnabled()) {
263             logger.warn("Could not validate request: " + ex.getMessage());
264         }
265         if (exceptionResolver != null) {
266             exceptionResolver.resolveException(messageContext, null, ex);
267         }
268         else {
269             if (logger.isDebugEnabled()) {
270                 logger.debug("No exception resolver present, creating basic soap fault");
271             }
272             SoapBody response = ((SoapMessage) messageContext.getResponse()).getSoapBody();
273             response.addClientOrSenderFault(ex.getMessage(), Locale.ENGLISH);
274         }
275         return false;
276     }
277 
278     /**
279      * Handles a fault exception.Default implementation logs the given exception, and creates a SOAP Fault with the
280      * properties of the given exception, and returns <code>false</code>.
281      *
282      * @param ex             the validation exception
283      * @param messageContext the message context
284      * @return <code>true</code> to continue processing the message, <code>false</code> (the default) otherwise
285      */
286     protected boolean handleFaultException(WsSecurityFaultException ex, MessageContext messageContext) {
287         if (logger.isWarnEnabled()) {
288             logger.warn("Could not handle request: " + ex.getMessage());
289         }
290         SoapBody response = ((SoapMessage) messageContext.getResponse()).getSoapBody();
291         SoapFault fault;
292         if (response instanceof Soap11Body) {
293             fault = ((Soap11Body) response).addFault(ex.getFaultCode(), ex.getFaultString(), Locale.ENGLISH);
294         }
295         else {
296             fault = response.addClientOrSenderFault(ex.getFaultString(), Locale.ENGLISH);
297         }
298         fault.setFaultActorOrRole(ex.getFaultActor());
299         return false;
300     }
301 
302     /**
303      * Abstract template method. Subclasses are required to validate the request contained in the given {@link
304      * SoapMessage}, and replace the original request with the validated version.
305      *
306      * @param soapMessage the soap message to validate
307      * @throws WsSecurityValidationException in case of validation errors
308      */
309     protected abstract void validateMessage(SoapMessage soapMessage, MessageContext messageContext)
310             throws WsSecurityValidationException;
311 
312     /**
313      * Abstract template method. Subclasses are required to secure the response contained in the given {@link
314      * SoapMessage}, and replace the original response with the secured version.
315      *
316      * @param soapMessage the soap message to secure
317      * @throws WsSecuritySecurementException in case of securement errors
318      */
319     protected abstract void secureMessage(SoapMessage soapMessage, MessageContext messageContext)
320             throws WsSecuritySecurementException;
321 
322     protected abstract void cleanUp();
323 }