Question

We're using spring security 3.0.5, Java 1.6 and Tomcat 6.0.32. In our .xml config file we've got:

<form-login login-page="/index.html" default-target-url="/postSignin.html" always-use-default-target="true"
 authentication-failure-handler-ref="authenticationFailureHandler"/>

and our authenticationFailureHandler defined as:

<beans:bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
   <beans:property name="exceptionMappings">
      <beans:props>
    <beans:prop key="org.springframework.security.authentication.BadCredentialsException">/index.html?authenticationFailure=true</beans:prop>
    </beans:props>
   </beans:property>
</beans:bean>

Java

    @RequestMapping(params={"authenticationFailure=true"}, value ="/index.html")
    public String handleInvalidLogin(HttpServletRequest request) {
       //...  How can I get the username that was used???
       // I've tried:
       Object username = request.getAttribute("SPRING_SECURITY_LAST_USERNAME_KEY");
       Object username = request.getAttribute("SPRING_SECURITY_LAST_USERNAME");  // deprecated
    }

So we're directing all BadCredentialsExceptions to the index.html and IndexController. In the IndexController I'd like to get the username that was used for the failed login attempt. How can I do this?

Was it helpful?

Solution

Okay so the answer turned out to be something extremely simple yet as far as I can tell, not greatly discussed or documented.

Here's all I had to do (no configurations anywhere just created this class)...

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
    private static final Logger LOG = Logger.getLogger(MyApplicationListener.class);

    @Override
    public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
        Object userName = event.getAuthentication().getPrincipal();
        Object credentials = event.getAuthentication().getCredentials();
        LOG.debug("Failed login using USERNAME [" + userName + "]");
        LOG.debug("Failed login using PASSWORD [" + credentials + "]");
    }
}

I'm far from a spring security expert so if anyone reads this and knows of a reason we shouldn't do it like this or knows a better way I'd love to hear about it.

OTHER TIPS

I did it this way:

  1. Created a class that extends SimpleUrlAuthenticationFailureHandler

  2. Overrid the onAuthenticationFailure method, which receives an HttpServletRequest as a parameter.

  3. request.getParameter("username"), where "username" is the name of my input in my HTML form.

You could instead supply your own version of DefaultAuthenticationEventPublisher and override the publishAuthenticationFailure method.

There doesn't seem to be much information on this solution, but if you set failureForwardUrl in your Spring Security configuration, you will be forwarded to the error page instead of redirected. You can then easily retrieve the username and password. For example, in your config add: .and().formLogin().failureForwardUrl("/login/failed") (* url must be different from the login page url)

And in your login controller, the following:

@RequestMapping(value = "/login/failed")
public String loginfailed(@ModelAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY) String user, @ModelAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY) String password) {
     // Your code here
}

SPRING_SECURITY_FORM_USERNAME_KEY, SPRING_SECURITY_FORM_PASSWORD_KEY are the default names, but you can set these in the FormLoginConfigurer as well:

.and().formLogin().usernameParameter("email").passwordParameter("password").failureForwardUrl("/login/failed")

I have found this works for me. Unfortunately, I still not found the exact location of this in SpringSecurity documents:

In any action, you can check the last-used username (no matter what the login was failed or not):

    String  username = (String) request.getSession().getAttribute("SPRING_SECURITY_LAST_USERNAME");

If you want to use Spring AOP, you can add the below code to your Aspect class:

private String usernameParameter = "username"; 

@Before("execution(* org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure(..))")
public void beforeLoginFailure(JoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = (HttpServletRequest) joinPoint.getArgs()[0];
        AuthenticationException exceptionObj = (AuthenticationException) joinPoint.getArgs()[2];

        String username = request.getParameter(usernameParameter);

        System.out.println(">>> Aspect check: AuthenticationException:  "+exceptionObj.getMessage());
        System.out.println(">>> Aspect check: user: "+ username + " failed to log in.");
}

This is a pretty old thread, but if you are using a relatively current "spring-boot-starter-security" package, here's how I did it:

I set my AuthenticationFailureHandler like so:

SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler("/my-error-url");
handler.setUseForward(true);

This will set the last exception into the request:

//from SimpleUrlAuthenticationFailureHandler source
request.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception);

Then from my controller I can get the bad username:

RequestMapping("/impersonate-error")
public String impersonateErrorPage(Map<String, Object> model, HttpServletRequest request) {

    AuthenticationException ex = (AuthenticationException)request.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
    if(ex != null) {
        logger.debug("Impersonate message: " + ex.getMessage());
        model.put("badName", ex.getMessage());
    }
    return "impersonate-error";
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top