/* * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.flex.security3; import java.io.IOException; import java.util.Collections; import java.util.Set; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.flex.core.ExceptionTranslator; import org.springframework.flex.http.AmfHttpMessageConverter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.util.CollectionUtils; import flex.messaging.MessageException; import flex.messaging.io.MessageIOConstants; import flex.messaging.io.amf.ActionMessage; import flex.messaging.io.amf.MessageBody; import flex.messaging.messages.ErrorMessage; import flex.messaging.messages.Message; /** * An {@link AuthenticationEntryPoint} implementation to be used in conjunction with an authentication * process that is completely driven by a Flex client, i.e. by presenting a Flex-based login UI and using * the client-side ChannelSet API to commence authentication. * * <p>Mostly this class exists to satisfy the requirements of Spring Security, where it requires an * <code>AuthenticationEntryPoint</code> to be provided to the {@link ExceptionTranslationFilter}. Only in * relatively exceptional cases (such as using the <code>intercept-url</code> tag to secure BlazeDS URLs, which is * not recommended in preference for using Spring BlazeDS's <code>secured-endpoint-path</code> and * <code>secured-channel</code> tags when using Remoting and Messaging destinations) should this implementation's * {@link #commence(HttpServletRequest, HttpServletResponse, AuthenticationException)} method ever actually * be invoked, as in the majority case security exceptions will never propagate out to the * <code>ExceptionTranslationFilter</code>, instead being converted to a {@link MessageException} by the * provided {@link ExceptionTranslator}s. One such exceptional case might be when using RESTful Spring MVC * endpoints to read and write AMF instead of the traditional RPC approach. * * <p>When this class is used in conjunction with the XML config namespace for Flex, it will be automatically * detected and its {@link #exceptionTranslators} will be configured automatically if they have not already been * set explicitly as part of bean configuration. * * @author Jeremy Grelle */ public class FlexAuthenticationEntryPoint extends Http403ForbiddenEntryPoint { private static final Log log = LogFactory.getLog(FlexAuthenticationEntryPoint.class); private static final ExceptionTranslator DEFAULT_TRANSLATOR = new SecurityExceptionTranslator(); private final AmfHttpMessageConverter converter = new AmfHttpMessageConverter(); private final MediaType amfMediaType = new MediaType("application", "x-amf"); private Set<ExceptionTranslator> exceptionTranslators; /** * If the incoming message is an {@link ActionMessage}, indicating a standard Flex Remoting or Messaging * request, invokes Spring BlazeDS's {@link ExceptionTranslator}s with the {@link AuthenticationException} and * sends the resulting {@link MessageException} as an AMF response to the client. * * <p>If the request is unabled to be deserialized to AMF, if the resulting deserialized object is not an * <code>ActionMessage</code>, or if no appropriate <code>ExceptionTranslator</code> is found, will simply * delegate to the parent class to return a 403 response. */ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { if (CollectionUtils.isEmpty(this.exceptionTranslators)) { exceptionTranslators = Collections.singleton(DEFAULT_TRANSLATOR); } HttpInputMessage inputMessage = new ServletServerHttpRequest(request); HttpOutputMessage outputMessage = new ServletServerHttpResponse(response); if (!converter.canRead(Object.class, inputMessage.getHeaders().getContentType())) { super.commence(request, response, authException); return; } ActionMessage deserializedInput = null; try { deserializedInput = (ActionMessage) this.converter.read(ActionMessage.class, inputMessage); } catch (HttpMessageNotReadableException ex) { log.info("Authentication failure detected, but request could not be read as AMF.", ex); super.commence(request, response, authException); return; } if (deserializedInput instanceof ActionMessage) { for (ExceptionTranslator translator : this.exceptionTranslators) { if (translator.handles(authException.getClass())) { MessageException result = translator.translate(authException); ErrorMessage err = result.createErrorMessage(); MessageBody body = (MessageBody) ((ActionMessage) deserializedInput).getBody(0); Message amfInputMessage = body.getDataAsMessage(); err.setCorrelationId(amfInputMessage.getMessageId()); err.setDestination(amfInputMessage.getDestination()); err.setClientId(amfInputMessage.getClientId()); ActionMessage responseMessage = new ActionMessage(); responseMessage.setVersion(((ActionMessage)deserializedInput).getVersion()); MessageBody responseBody = new MessageBody(); responseMessage.addBody(responseBody); responseBody.setData(err); responseBody.setTargetURI(body.getResponseURI()); responseBody.setReplyMethod(MessageIOConstants.STATUS_METHOD); converter.write(responseMessage, amfMediaType, outputMessage); response.flushBuffer(); return; } } } super.commence(request, response, authException); } public Set<ExceptionTranslator> getExceptionTranslators() { return exceptionTranslators; } public void setExceptionTranslators( Set<ExceptionTranslator> exceptionTranslators) { this.exceptionTranslators = exceptionTranslators; } }