We\'re using spring security 3.0.5, Java 1.6 and Tomcat 6.0.32. In our .xml config file we\'ve got:
You could instead supply your own version of DefaultAuthenticationEventPublisher and override the publishAuthenticationFailure method.
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";
}
I did it this way:
Created a class that extends SimpleUrlAuthenticationFailureHandler
Overrid the onAuthenticationFailure method, which receives an HttpServletRequest as a parameter.
request.getParameter("username"), where "username" is the name of my input in my HTML form.
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")
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.");
}
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.