I'm developing an application in which I need to catch and respond to Authentication events to take appropriate action. Currently, I'm catching just fine the AuthenticationSuccessEvent
Spring throws when a user logs in manually. I'm now trying to implement Remember-Me functionality. Logging helped me to figure out the the event I want to catch is the InteractiveAuthenticationSuccessEvent
. Can someone take a gander at the code below and help me to respond to this new event?
@Override
public void onApplicationEvent(ApplicationEvent event) {
log.info(event.toString()); // debug only: keep track of all events
if (event instanceof AuthenticationSuccessEvent) {
AuthenticationSuccessEvent authEvent = (AuthenticationSuccessEvent)event;
lock.writeLock().lock();
try {
sessionAuthMap.put(((WebAuthenticationDetails)authEvent.getAuthentication().getDetails()).getSessionId(), authEvent.getAuthentication());
} finally {
lock.writeLock().unlock();
}
} else if (event instanceof HttpSessionDestroyedEvent) {
HttpSessionDestroyedEvent destroyEvent = (HttpSessionDestroyedEvent)event;
lock.writeLock().lock();
try {
sessionAuthMap.remove(destroyEvent.getId());
} finally {
lock.writeLock().unlock();
}
}
}
Additional Information:
I didn't mention in the original posting that the requirement of storing the Session Id and Authentication object in a Map is due to the fact that I'm using the Google Earth plugin. GE acts as a separate, unrelated user agent, and thus the user's session information never gets passed to the server by GE. For this reason, I rewrite the request URL from GE to contain the user's active Session Id (from the aforementioned Map) as a parameter so we can verify that said Session Id is indeed valid for a logged in user. All of this is in place because we have KML which GE needs, but we can't allow a user to pick up a direct, unprotected URL via Firebug or what have you.
Spring Config: (sorry, SO kinda fudged the formatting)
<sec:http use-expressions="true">
<sec:intercept-url pattern="/Login.html*" access="permitAll"/>
<sec:intercept-url pattern="/j_spring_security*" access="permitAll" method="POST"/>
<sec:intercept-url pattern="/main.css*" access="permitAll"/>
<sec:intercept-url pattern="/favicon.ico*" access="permitAll"/>
<sec:intercept-url pattern="/images/**" access="permitAll"/>
<sec:intercept-url pattern="/common/**" access="permitAll"/>
<sec:intercept-url pattern="/earth/**" access="permitAll"/>
<sec:intercept-url pattern="/earth/kml/**" access="permitAll"/>
<sec:intercept-url pattern="/earth/js/**" access="permitAll"/>
<sec:intercept-url pattern="/css/**" access="permitAll"/>
<sec:intercept-url pattern="/resource*" access="permitAll"/>
<sec:intercept-url pattern="/geom*" access="hasRole('ROLE_SUPERUSER')"/>
<sec:intercept-url pattern="/status/**" access="permitAll"/>
<sec:intercept-url pattern="/index.html*" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/project.html*" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/js/**" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/help/**" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/app/**" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/data/**" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
<sec:intercept-url pattern="/session/**" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/" access="hasRole('ROLE_USER')"/>
<sec:intercept-url pattern="/**" access="denyAll"/>
<sec:intercept-url pattern="**" access="denyAll"/>
<sec:session-management session-fixation-protection="none" />
<sec:form-login login-page="/Login.html${dev.gwt.codesrv.htmlparam}" default-target-url="/index.html${dev.gwt.codesrv.htmlparam}" authentication-failure-url="/Login.html${dev.gwt.codesrv.htmlparam}"/>
<sec:http-basic/>
<sec:logout invalidate-session="true" logout-success-url="/Login.html${dev.gwt.codesrv.htmlparam}"/>
<sec:remember-me key="[REMOVED]" />
</sec:http>
<bean id="authenticationEventPublisher" class="org.springframework.security.authentication.DefaultAuthenticationEventPublisher" />
<bean id="org.springframework.security.authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<property name="authenticationEventPublisher" ref="authenticationEventPublisher"/>
<property name="providers">
<list>
<ref bean="authenticationProvider" />
<ref bean="anonymousProvider" />
</list>
</property>
</bean>
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="passwordEncoder" ref="passwordEncoder"/>
<property name="saltSource" ref="saltSource"/>
<property name="userDetailsService" ref="userService" />
</bean>
<bean id="anonymousProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="[REMOVED]" />
</bean>
Please read the update at bottom of this post
Have you tried just adding another "else if" based on "event instance of InteractiveAuthenticationSuccessEvent"?
@Override
public void onApplicationEvent(ApplicationEvent event) {
log.info(event.toString()); // debug only: keep track of all events
if (event instanceof AuthenticationSuccessEvent) {
AuthenticationSuccessEvent authEvent = (AuthenticationSuccessEvent)event;
lock.writeLock().lock();
try {
sessionAuthMap.put(((WebAuthenticationDetails)authEvent.getAuthentication().getDetails()).getSessionId(), authEvent.getAuthentication());
} finally {
lock.writeLock().unlock();
}
} else if (event instanceof InteractiveAuthenticationSuccessEvent) {
InteractiveAuthenticationSuccessEvent authEvent = (InteractiveAuthenticationSuccessEvent)event;
lock.writeLock().lock();
try {
sessionAuthMap.put(((WebAuthenticationDetails)authEvent.getAuthentication().getDetails()).getSessionId(), authEvent.getAuthentication());
} finally {
lock.writeLock().unlock();
}
} else if (event instanceof HttpSessionDestroyedEvent) {
HttpSessionDestroyedEvent destroyEvent = (HttpSessionDestroyedEvent)event;
lock.writeLock().lock();
try {
sessionAuthMap.remove(destroyEvent.getId());
} finally {
lock.writeLock().unlock();
}
}
}
UPDATE: Your question is basically, "How can I get one http client (i.e. the Google Earth plugin) to appear authenticated to my site as someone who logged in using another http client (the user's browser)?" Even if you could get that to work, it doesn't seem like a good idea, security-wise. Another interesting question would be, "How can I load KML into the Google Earth plugin other than by having the plugin request the KML file over http?" According to their docs, there is a method, parsekml(), which takes a String containing KML data. So in theory you could load the protected KML data using a JavaScript/AJAX call from the user's browser, which would be compatible with your site's normal security setup, then pass the returned KML to parsekml().
According to the spring docs, "In Spring Security 3, the user is first authenticated by the AuthenticationManager and once they are successfully authenticated, a session is created."
Instead, you could implement your own AuthenticationSuccessHandler
(probably by subclassing SavedRequestAwareAuthenticationSuccessHandler
). You can put whatever logic you want in the onAuthenticationSuccess
method, so move your existing logic there:
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
// declare and initialize lock and sessionAuthMap at some point...
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
lock.writeLock().lock();
try {
sessionAuthMap.put(request.getSession().getId(), authentication);
} finally {
lock.writeLock().unlock();
}
super.onAuthenticationSuccess(request, response, authentication);
}
}
Then, update your configs so that Spring Security invokes this class during the authentication process. Here's how:
Step 1: customize the UsernamePasswordAuthenticationFilter
which is created by the <form-login>
element. In particular, put this into your <http>
element:
<sec:custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
Step 2: define myFilter, and hook MyAuthenticationSuccessHandler
into it.
<bean id="myFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationFailureHandler" ref="myAuthenticationSuccessHandler" />
<property name="authenticationSuccessHandler" ref="myAuthenticationFailureHandler" />
</bean>
<bean id="myAuthenticationSuccessHandler"
class="my.MyAuthenticationSuccessHandler">
<!-- set properties here -->
</bean>
<!-- you can subclass this or one of its parents, too -->
<bean id="myAuthenticationFailureHandler"
class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
<!-- set properties such as exceptionMappings here -->
</bean>
For more details, see http://static.springsource.org/spring-security/site/docs/3.0.x/reference/ns-config.html. Also see the AbstractAuthenticationProcessingFilter docs.
BTW your problem reminds me of OAuth. Essentially you're issuing an access token to the client as a result of the resource owner authorization.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With