View Javadoc

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