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 * {@link org.springframework.ws.server.endpoint.mapping.AbstractEndpointMapping#setInterceptors(org.springframework.ws.server.EndpointInterceptor[])
42 * endpoint mapping}; or on the client side, on the {@link org.springframework.ws.client.core.WebServiceTemplate#setInterceptors(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 {@link 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 {@link #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
108 * if the {@link #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 {@link #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
140 * if the {@link #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 {@link #secureMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
192 * if the {@link #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 {@link #validateMessage(org.springframework.ws.soap.SoapMessage,org.springframework.ws.context.MessageContext)}
220 * if the {@link #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 {@link
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 {@link
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 {@link
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 }