View Javadoc

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