I wish someone can help me with this one. I have a Spring MVC application (Spring 3) running perfectly fine with Spring Security 3, we are now adding support for Flex and added BlazeDS to the application and Spring Integration (1.5.0 M2), all started working fine until we wanted to integrate authentication through Spring Security. The Flex application is a "mini" UI that serves as a P2P chat (through messaging) between two users and it is embedded in a JSP page in the Spring MVC application, what we want to do is ensure (from the Flex application) that the user is logged in before showing the chat UI. The authentication is done from the Spring MVC application (a web form) and it works fine, but every time we access to the Spring MVC page that holds the Flex application and make a remoting call from Flex to get the current user details we get an exception:
flex.messaging.security.SecurityException: An Authentication object was not found in the SecurityContext
We assumed that the remoting request (made from an authenticated session) will be somehow picked up and recognized and that the Flex client doesn't need to authenticate again. What could be wrong here? Here is my spring security config and my flex configuration file as well as the web.xml:
security.xml:
<bean id="springSecurityFilterChain"
class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain filters="none" pattern="/styles/**" />
<sec:filter-chain filters="none" pattern="/js/**" />
<sec:filter-chain filters="none" pattern="/images/**" />
<sec:filter-chain
filters="securityContextPersistenceFilter,
logoutFilter,
usernamePasswordAuthenticationFilter,
anonymousAuthenticationFilter,
exceptionTranslationFilter,
menuLoaderRequestFilter,
filterSecurityInterceptor"
pattern="/web/**" />
<sec:filter-chain
filters="securityContextPersistenceFilter,
usernamePasswordAuthenticationFilter,
exceptionTranslationFilter"
pattern="/do_login" />
<sec:filter-chain
filters="securityContextPersistenceFilter,
logoutFilter,
exceptionTranslationFilter"
pattern="/do_logout" />开发者_JAVA百科;
</sec:filter-chain-map>
</bean>
<bean id="securityContextPersistenceFilter"
class="org.springframework.security.web.context.SecurityContextPersistenceFilter" />
<bean id="usernamePasswordAuthenticationFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="filterProcessesUrl" value="/do_login"/>
<property name="authenticationFailureHandler">
<ref bean="loginFailureHandler" />
</property>
<property name="authenticationSuccessHandler">
<ref bean="loginSuccessHandler" />
</property>
<property name="usernameParameter" value="login_user" />
<property name="passwordParameter" value="login_password" />
</bean>
<bean id="loginFailureHandler"
class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/web/login?error=login.failure"/>
<property name="exceptionMappings">
<map>
<entry>
<key>
<value>org.springframework.security.authentication.AuthenticationServiceException</value>
</key>
<value>/web/login?error=login.database.failure</value>
</entry>
</map>
</property>
</bean>
<bean id="loginSuccessHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/web/index"/>
</bean>
<bean id="anonymousAuthenticationFilter"
class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="userAttribute"
value="anonymousUser,ROLE_ANONYMOUS" />
<property name="key" value="AD17JFJ005P00Z7MK" />
</bean>
<bean id="logoutFilter"
class="org.springframework.security.web.authentication.logout.LogoutFilter">
<!-- the post-logout destination -->
<constructor-arg value="/web/login?success=login.loggedout"/>
<constructor-arg>
<array>
<ref bean="logoutHandler" />
</array>
</constructor-arg>
<property name="filterProcessesUrl" value="/do_logout"/>
</bean>
<bean id="logoutHandler"
class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
<bean id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint"
ref="mainEntryPoint"/>
<property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</bean>
<bean id="mainEntryPoint"
class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
<constructor-arg>
<map>
<entry>
<key>
<value>hasHeader('X-Requested-With', 'XMLHttpRequest')</value>
</key>
<ref bean="ajaxEntryPoint"/>
</entry>
<entry>
<key>
<value>hasHeader('Content-type', 'application/x-amf')</value>
</key>
<ref bean="flexEntryPoint" />
</entry>
</map>
</constructor-arg>
<property name="defaultEntryPoint" ref="defaultEntryPoint" />
</bean>
<bean id="entryPointTemplate" abstract="true">
<property name="loginFormUrl" value="/web/login"/>
</bean>
<bean id="ajaxEntryPoint" parent="entryPointTemplate"
class="com.saes.support.security.AjaxAuthenticationEntryPoint" >
</bean>
<bean id="defaultEntryPoint" parent="entryPointTemplate"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
</bean>
<bean id="flexEntryPoint" class="org.springframework.flex.security3.FlexAuthenticationEntryPoint">
</bean>
<bean id="accessDeniedHandler"
class="com.saes.support.security.SAESAccessDeniedHandler">
<property name="errorPage"
value="/web/errors/accessDenied"/>
</bean>
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager"
ref="authenticationManager"/>
<property name="accessDecisionManager" ref="decisionManager"/>
<property name="securityMetadataSource">
<sec:filter-security-metadata-source>
<sec:intercept-url pattern="/web/profile/**"
access="ROLE_USER" />
<sec:intercept-url pattern="/web/doctor/**"
access="ROLE_DOCTOR" />
</sec:filter-security-metadata-source>
</property>
</bean>
<bean id="menuLoaderRequestFilter"
class="com.saes.security.menu.MenuPermissionsAdapterRequestFilter">
</bean>
<bean id="decisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<ref bean="roleVoter"/>
<ref bean="authenticatedVoter"/>
</list>
</property>
</bean>
<bean id="roleVoter"
class="org.springframework.security.access.vote.RoleVoter" />
<bean id="authenticatedVoter"
class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService">
<ref bean="userDetailsService" />
</property>
</bean>
<bean id="anonymousAuthenticationProvider"
class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="AD17JFJ005P00Z7MK"/>
</bean>
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider" />
<ref bean="anonymousAuthenticationProvider" />
</list>
</property>
</bean>
flex-servlet.xml:
<flex:message-broker
services-config-path="/WEB-INF/config/flex/services-config.xml">
<flex:secured authentication-manager="authenticationManager"
access-decision-manager="decisionManager">
<flex:secured-endpoint-path pattern="**/messagebroker/*" access="ROLE_USER"/>
</flex:secured>
</flex:message-broker>
web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/spring/persistence.xml
/WEB-INF/config/spring/security.xml
/WEB-INF/config/spring/services.xml
/WEB-INF/config/spring/facade.xml
/WEB-INF/config/spring/validator.xml
/WEB-INF/config/flex/flex-context.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>mainServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mainServlet</servlet-name>
<url-pattern>/web/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>flex</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>flex</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/jsp/errors/critical-error.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/errors/404.jsp</location>
</error-page>
And here is the relevant part of the Flex code:
<s:ChannelSet id="chatChannelSet">
<s:StreamingAMFChannel url="http://192.168.1.3:8080/MyApp/messagebroker/streamamf">
</s:StreamingAMFChannel>
</s:ChannelSet>
<s:ChannelSet id="remotingChannelSet">
<s:AMFChannel url="http://192.168.1.3:8080/MyApp/messagebroker/amf">
</s:AMFChannel>
</s:ChannelSet>
<s:RemoteObject id="remoteService"
destination="remoteService"
channelSet="{remotingChannelSet}">
</s:RemoteObject>
var asyncCall:AsyncToken = remoteService.getTicketForCurrentUser();
asyncCall.addResponder(new Responder(getTicket_Result, getTicket_Fault));
The previous code always ends up in the Fault handler with the error mentioned at the beginning of the cuestion
Other configuration files:
services-config.xml:
<services-config>
<services>
<service-include file-path="messaging-config.xml" />
</services>
<channels>
<channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
<channel-definition id="streaming-amf" class="mx.messaging.channels.StreamingAMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/streamamf" class="flex.messaging.endpoints.StreamingAMFEndpoint"/>
<properties>
<idle-timeout-minutes>0</idle-timeout-minutes>
<max-streaming-clients>10</max-streaming-clients>
<server-to-client-heartbeat-millis>5000</server-to-client-heartbeat-millis>
<user-agent-settings>
<user-agent match-on="MSIE" kickstart-bytes="2048" max-streaming-connections-per-session="3" />
<user-agent match-on="Firefox" kickstart-bytes="2048" max-streaming-connections-per-session="3" />
</user-agent-settings>
</properties>
</channel-definition>
</channels>
<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Debug">
<properties>
<prefix>[BlazeDS] </prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
<filters>
<pattern>Endpoint.*</pattern>
<pattern>Service.*</pattern>
<pattern>Configuration</pattern>
</filters>
</target>
</logging>
<system>
<redeploy>
<enabled>false</enabled>
</redeploy>
</system>
messaging-config.xml:
<service id="message-service"
class="flex.messaging.services.MessageService">
<adapters>
<adapter-definition id="actionscript" class="flex.messaging.services.messaging.adapters.ActionScriptAdapter" default="true" />
</adapters>
<destination id="chat-destination">
<properties>
<server>
<message-time-to-live>0</message-time-to-live>
<allow-subtopics>true</allow-subtopics>
<subtopic-separator>.</subtopic-separator>
<disallow-wildcard-subtopics>true</disallow-wildcard-subtopics>
</server>
</properties>
<channels>
<channel ref="streaming-amf" />
</channels>
</destination>
<default-channels>
<channel ref="streaming-amf"/>
</default-channels>
After some digging into the configuration I found the problem, and I will post the solution for everyone who might need it. I had to explicitly tell Spring Security to include the "securityContextPersistenceFilter" in the filter chain for my "/messagebroker/**" urls so the security context gets properly populated with the authentication information (as we assumed from the beginning). The configuration was added to the "springSecurityFilterChain" bean as follows:
<bean id="springSecurityFilterChain"
class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<!-- other filter chain maps and options here (see the entire file in comment above -->
<sec:filter-chain
filters="securityContextPersistenceFilter"
pattern="/messagebroker/**" />
</bean>
After adding that filter chain configuration to the Spring Security filter all requests from the Flex UI where automatically populated with the existing authentication created from a previous login through the Spring MVC web form.
To give a quick answer - can you set up your Flex stuff to piggyback on an existing Spring Security HTML-based login form? Yes, absolutely you can. I've done it and it works quite nicely if you have some of your app that needs to be protected but doesn't get served through a flex app.
I'm NOT using the
<flex:secured>
part of the the message-broker config (it didn't exist when I first set this up, perhaps I could switch to it now but since my setup is working I don't feel any strong need to at the moment). I have my my messagebroker url pattern set up to be secured. My messagebroker is secured like so:
(the swf-loading-page is just a placeholder name for a page that wraps the swf... and .do is mapped to my view resolver)
<security:intercept-url pattern="/swf-loading-page.do*" access="ROLE_USER,
<security:intercept-url pattern="/messagebroker/**" access="ROLE_USER" />
When on the flex side you call:
remoteService.getTicketForCurrentUser()
What gets called on the server in the java side?
For mine, I do something similar - I pull out the local user to return it to the flex client (to display the current logged in user, etc) -
On your Java side you can use the SpringSecurityContextHolder to get the SecurityContext and then from the SecurityContext you can pull out the
//These packages are where you can hook into the authentication from your service
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
...
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
If you your your app server with jpda debugging you can set a breakpoint on that method and peek into your security context and make sure it is being correctly populated (but it seems that it is not - which is odd)
精彩评论