问题
I want to log if a user tries to authenticate with wrong credentials. Therefore i have added this event listener class to my project:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.stereotype.Component;
@Component
public class AuthenticationFailureListener
implements ApplicationListener<AuthenticationFailureBadCredentialsEvent>{
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
System.out.println("test");
logger.info("test2");
}
}
Problem is it does not work at all. I use Spring Security default login page. The page shows "bad credentials" error when using wrong credentials, but my method above does not get called. I have very similar code for a success event listener, which works wonderfully:
@Component
public class AuthenticationSuccessListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired private UserService users;
@Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
User user = users.get(event.getAuthentication().getName());
boolean isAdmin = user.getRole().equals(User.ROLE_ADMIN);
logger.info((isAdmin ? "Admin" : "User") + " with id " + user.getIdLink()
+ " has successfully logged in!");
}
}
Here is my Spring Security Java Configuration:
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.and()
.httpBasic();
}
}
I have no clue whats going on here, help appreciated a lot!
Spring version: 4.0.9
Spring Security version: 3.2.5 (also tried 4.0.1)
Edit:
Okay, i set log level to DEBUG for Spring, but nothing. I searched for every occurance of "Listener" and the log states that instances of AuthenticationFailureListener as well as AuthenticationSuccessListeners have been created without any error.
I even put the log into diff tool (after replacing all times & censoring) and compared with a code version where FailureListener code is commented out, but didn't find something. You can search it yourself if you want to:
https://www.diffchecker.com/cwdn4sp4
On the bottom of the page you will find the plain log text on the left side.
Edit2: Partly Solved
Serges solution helped, here is my complete implementation of the onAuthenticationFailure method:
@Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof BadCredentialsException) {
String name = request.getParameter("username");
String password = request.getParameter("password");
Authentication auth =
new UsernamePasswordAuthenticationToken(name, password);
eventPublisher.publishEvent(
new AuthenticationFailureBadCredentialsEvent(auth, exception));
}
super.onAuthenticationFailure(request, response, exception);
}
回答1:
That is by design.
Javadoc for AbstractAuthenticationProcessingFilter
is clear on that :
Event Publication :
If authentication is successful, an InteractiveAuthenticationSuccessEvent will be published via the application context. No events will be published if authentication was unsuccessful, because this would generally be recorded via an AuthenticationManager-specific application event.
(emphasize mine)
If you want to send explicitely an event for authentication failures, you could use a custom AuthenticationFailureHandler
extending SimpleUrlAuthenticationFailureHandler
that would send the event and call base class onAuthenticationFailure
method.
public class EventSendingAuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler,
implements ApplicationEventPublisherAware {
protected ApplicationEventPublisher eventPublisher;
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Override
void onAuthenticationFailure(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
AuthenticationException exception)
throws IOException,
javax.servlet.ServletException {
// use eventPublisher to publish the event according to exception
super.onAuthenticationFailure(request, response, exception);
}
}
You should be able to configure it that way :
@Bean
AuthenticationFailureHandler eventAuthenticationFailureHandler() {
return new EventSendingAuthenticationFailureHandler();
}
@Autowired
AuthenticationFailureHandler eventAuthenticationFailureHandler;
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().failureHandler(eventAuthenticationFailureHandler)
.and()
.httpBasic();
}
回答2:
I got this working in a different way.
@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
// Inject applicationEventPublisher
@Inject
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
// configure a auth event publisher
.authenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher))
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.and()
.httpBasic();
}
}
With these changes, my event listener was able to receive auth failure events. This is with spring-security 4.0.2.RELEASE and spring-boot 1.2.5.RELEASE
Hope it helps.
回答3:
The LoggerListener works fine with Spring Security 3.2.8. See Grepcode for source code.
Your adpoted code:
@Named
public class AuthenticationSuccessListener implements ApplicationListener<AbstractAuthenticationEvent> {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Inject
private UserService users;
@Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
if (event instanceof InteractiveAuthenticationSuccessEvent) {
User user = users.get(event.getAuthentication().getName());
boolean isAdmin = user.getRole().equals(User.ROLE_ADMIN);
logger.info((isAdmin ? "Admin" : "User") + " with id " + user.getIdLink() + " has successfully logged in!");
}
}
}
来源:https://stackoverflow.com/questions/30278293/spring-badcredentials-event-not-firing