【问题标题】:Spring BadCredentials Event not firingSpring BadCredentials 事件未触发
【发布时间】:2015-07-28 11:41:42
【问题描述】:

如果用户尝试使用错误的凭据进行身份验证,我想记录。因此,我已将此事件侦听器类添加到我的项目中:

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");
    }
}

问题是它根本不起作用。我使用 Spring Security 默认登录页面。该页面在使用错误凭据时显示“错误凭据”错误,但我上面的方法没有被调用。 我有一个非常相似的成功事件监听器代码,效果很好:

@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!");
    }
}

这是我的 Spring Security Java 配置:

@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();
    }
}

我不知道这里发生了什么,非常感谢您的帮助!

春季版:4.0.9
Spring Security 版本:3.2.5(也试过 4.0.1)


编辑:

好的,我将 Spring 的日志级别设置为 DEBUG,但没有。我搜索了“Listener”的每一次出现,并且日志指出 AuthenticationFailureListener 以及 AuthenticationSuccessListeners 的实例已创建且没有任何错误。

我什至将日志放入 diff 工具中(在替换所有时间和审查之后),并与注释掉 FailureListener 代码的代码版本进行比较,但没有找到任何东西。想的话可以自行搜索:
https://www.diffchecker.com/cwdn4sp4
在页面底部,您会在左侧找到纯日志文本。


Edit2:部分解决

Serges 解决方案有帮助,这是我对 onAuthenticationFailure 方法的完整实现:​​

@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);
}

【问题讨论】:

  • 您是否设置了适当的组件扫描,以便将您的AuthenticationFailureListener 创建为 bean?启用 Spring 调试日志记录以验证它是否实际创建。这将为您/我们提供更多信息。
  • FailureListener 与 SuccessListener 位于同一个包中,因此 scan 应该同时适用于两者。我现在尝试调试日志记录,谢谢!

标签: java spring spring-security


【解决方案1】:

我让这个工作以不同的方式工作。

@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();
    }
}

通过这些更改,我的事件侦听器能够接收身份验证失败事件。 这是 spring-security 4.0.2.RELEASE 和 spring-boot 1.2.5.RELEASE

希望对您有所帮助。

【讨论】:

  • 你能把整个例子贴出来吗?
【解决方案2】:

这是设计

AbstractAuthenticationProcessingFilter 的 Javadoc 很清楚:

活动发布:

如果身份验证成功,将通过应用程序上下文发布 InteractiveAuthenticationSuccessEvent。 如果身份验证不成功,则不会发布任何事件,因为这通常会通过 AuthenticationManager 特定的应用程序事件进行记录。

(强调我的)

如果您想显式发送身份验证失败事件,您可以使用自定义 AuthenticationFailureHandler 扩展 SimpleUrlAuthenticationFailureHandler 来发送事件并调用基类 onAuthenticationFailure 方法。

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);
    }
}

你应该可以这样配置它:

@Bean
AuthenticationFailureHandler eventAuthenticationFailureHandler() {
    return new EventSendingAuthenticationFailureHandler();
}

@Autowired
AuthenticationFailureHandler eventAuthenticationFailureHandler;

protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .formLogin().failureHandler(eventAuthenticationFailureHandler)
            .and()
        .httpBasic();
}

【讨论】:

  • 还有一个问题:.formLogin().failureHandler(handler) 破坏了生成的登录页面重定向错误凭据(即使我在我的处理程序构造函数中调用super("/login?error"))。它会导致 No mapping found for HTTP request with URI [/GMM/login] in DispatcherServlet with name 'GMM' 并在使用错误凭据时返回 404 页面。
  • 我认为这只是处理程序的配置问题。您的登录页面的 URL 是什么?如果您直接在浏览器中输入该 URL (http://host/context/xxx),最后与 ?error 相同,会发生什么?
  • 我的登录页面 URL 目前是 localhost:8080/GMM/login 直接输入不会改变任何行为。 /login?error 在所有情况下都返回 404。
  • 好的,我无法解决问题,但可以通过使用自定义登录页面来规避它。
  • 在我的例子中,我重写了protected void configure(AuthenticationManagerBuilder auth) 来自定义我的AuthenticationManager,这破坏了这个事件的发送。我需要添加auth. authenticationEventPublisher(authenticationEventPublisher()),其中authenticationEventPublisher() 是一个@Bean,它返回一个新的DefaultAuthenticationEventPublisher()
【解决方案3】:

LoggerListener 在 Spring Security 3.2.8 中运行良好。源码见Grepcode

您采用的代码:

@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!");
        }
    }
}

【讨论】:

    猜你喜欢
    • 2020-04-15
    • 1970-01-01
    • 1970-01-01
    • 2010-11-08
    • 2019-01-31
    • 2013-04-09
    • 2015-07-25
    • 2017-06-25
    • 2012-02-21
    相关资源
    最近更新 更多