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 }