Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security authenticationSuccessHandler for RememberMeAuthenticationFilter

I want to implement a custom AuthenticationSuccessHandler for my login filter which is org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.

Here is my spring security configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans 
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <security:http entry-point-ref="restAuthenticationEntryPoint" disable-url-rewriting = "true" auto-config="true" use-expressions="true">
        <security:intercept-url pattern="/api/*" access="hasRole('AUTHENTICATED_USER')"/>
        <security:remember-me key="spring_login_detail" services-ref="rememberMeServices"/>
        <security:form-login login-processing-url="/login"/>

        <security:logout
            invalidate-session="true"
            delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE"
            logout-url="/logout" 
        />
    </security:http>

    <security:global-method-security secured-annotations="enabled"  pre-post-annotations="enabled"/>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="rememberMeAuthenticationProvider"/>
        <security:authentication-provider user-service-ref="customUserDetailsService">
            <security:password-encoder ref="passwordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>

    <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

    <bean id="mySuccessHandler" class="com.projectname.security.CustomSavedRequestAwareAuthenticationSuccessHandler"/>

    <bean id="customUserDetailsService" class="com.projectname.security.CustomUserDetailsService"/>

    <bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
        <property name="key" value="jsfspring-sec" />
        <property name="userDetailsService" ref="customUserDetailsService" />
        <property name="alwaysRemember" value="false" />
        <property name="tokenValiditySeconds" value="1209600" />
        <property name="parameter" value="_spring_security_remember_me_input"/>
    </bean>

    <bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
        <property name="key" value="spring_login_detail"/>
    </bean>

    <bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
        <property name="rememberMeServices" ref="rememberMeServices"/>
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationSuccessHandler" ref="mySuccessHandler"/>
    </bean> 

</beans>

Here's my custom AuthenticationSuccessHandler implementation

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;

public class CustomSavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest == null) {
            clearAuthenticationAttributes(request);
            return;
        }

        String targetUrlParam = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
                || (targetUrlParam != null
                && StringUtils.hasText(request.getParameter(targetUrlParam)))) {
            requestCache.removeRequest(request, response);
            clearAuthenticationAttributes(request);
            return;
        }

        clearAuthenticationAttributes(request);
    }

    public void setRequestCache(RequestCache requestCache) {
        this.requestCache = requestCache;
    }
}

The problem is after a successful authentication the onAuthenticationSuccess isn't called at all. I've read an answer on StackOverflow that says that I need to implement onAuthenticationSuccess instead of SimpleUrlAuthenticationSuccessHandler. I've tried to do that, still didn't work. Everything else is fine, the only problem is each time I log in, spring just redirected me to '/'. Which is not what I want, I want it just to return '200 OK'

like image 412
William Wino Avatar asked Oct 03 '22 22:10

William Wino


1 Answers

Assuming that I've understood correctly, you want to break the filter chain and send an HTTP response code when an authentication succeeds no matter how the authentication happens; i.e. it can be login form or remember-me authentication.

So, first, add the following logic to your CustomSavedRequestAwareAuthenticationSuccessHandler:

// place where applicable
if (authentication != null) {
  response.setStatus(HttpServletResponse.SC_OK);
}

Second, define a new filter such as :

class HttpResponseAuthenticationFilter extends RememberMeAuthenticationFilter {

  protected  void   onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
    super.onSuccessfulAuthentication(request, response, authResult);
    if (authResult != null) {
      response.setStatus(HttpServletResponse.SC_OK);
    }
  }

} 

Third, define the customer filer in security:http section as:

<custom-filter position="LAST" ref="myHttpResponseAuthFilter" />

Fourth, add a reference for your success handler to your form-login as:

<form-login ... authentication-success-handler-ref="mySuccessHandler" ... />

because this is missing from your form authentication.

Additionally, based on Spring Security documentation on filter positions, it is advised that you do not use auto-config with custom filters.

Observe that:

  1. The reason you see this behavior is that when you see the login form, it's not about the remember-me services. The form processing decided the final target URL.
  2. After the first time, it'd be the remember-me filter that authenticates and again needs to send an HTTP response code.

I also suggest reading this answer as it gives more insight into the difference between form-login, http-basic auth, and remember-me services in Spring Security.

like image 130
nobeh Avatar answered Oct 13 '22 11:10

nobeh