Spring Security AuthenticationFailureHandler与AuthenticationFailureEvent

时间:2013-08-26 19:18:13

标签: spring jsf spring-security

我正在使用spring-security 3.1.4并且我有要求:

  • 监视身份验证是成功还是失败
    • 如果成功将用户常规信息放入会话属性
    • 如果结果是失败那么;
      • 确定失败原因(帐户已锁定,帐户已过期,凭据已过期,用户已停用,登录失败尝试次数超过等)
      • 为growl组件生成登录失败消息,该消息位于login.xhtml
      • 采取针对失败事件的具体行动,例如:在错误凭据上增加db中的登录失败尝试和/或重定向到重新定义凭据等页面

我已经研究过并找到了三种解决方案:

  • 实施PhaseListener这是草率的,因为它在中被调用 所有阶段活动:

public class LoginErrorPhaseListener implements PhaseListener {
  private static final long   serialVersionUID              = -404551400448242299L;

  private static final String MESSAGES_RESOURCE_BUNDLE_NAME = "msgs";
  private static final String ACCESS_DENIED_MESSAGE_KEY     = "accessDeniedMessage";
  private static final String BAD_CREDENTIALS_MESSAGE_KEY   = "badCredentialsMessage";

  @Override
  public void beforePhase(final PhaseEvent arg0) {
    Exception e = (Exception) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(WebAttributes.AUTHENTICATION_EXCEPTION);
      if (e instanceof BadCredentialsException) {
         FacesContext fc = FacesContext.getCurrentInstance();
         ResourceBundle messages = fc.getApplication().getResourceBundle(fc, MESSAGES_RESOURCE_BUNDLE_NAME);
         fc.getExternalContext().getSessionMap().put(WebAttributes.AUTHENTICATION_EXCEPTION, null);
         fc.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, messages.getString(ACCESS_DENIED_MESSAGE_KEY), messages.getString(BAD_CREDENTIALS_MESSAGE_KEY)));
      }
   }

   @Override
   public void afterPhase(final PhaseEvent arg0) {
   }

   @Override
   public PhaseId getPhaseId() {
      return PhaseId.RENDER_RESPONSE;
   }

}
  • 其他正在自定义AuthenticationFailureHandlerAuthenticationSuccessHandler

public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

   @Inject
   private UserDao userDao;

   public CustomAuthenticationFailureHandler() {
   }

   public CustomAuthenticationFailureHandler(String defaultFailureUrl) {
      super(defaultFailureUrl);
   }

   @Override
   public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
      super.onAuthenticationFailure(request, response, exception);
      Class exceptionClazz = exception.getClass();

      if (exceptionClazz == UsernameNotFoundException.class) {

      }
      else if (exceptionClazz == AuthenticationCredentialsNotFoundException.class) {

      }
      else if (exceptionClazz == BadCredentialsException.class) {
         UserBean user = (UserBean) exception.getExtraInformation();
         if (user.getLoginAttempts() == 2) {
            userDao.updateUserStates(user.getUsername(), true, false, true, true);
            userDao.resetUserLoginFailedAttempts(user.getUsername());
         }
         else {
            userDao.incrementLoginFailedAttempts(user.getUsername());
         }
      }
      else if (exceptionClazz == AccountStatusException.class) {

      }
      else if (exceptionClazz == AuthenticationServiceException.class) {

      }
      else if (exceptionClazz == InsufficientAuthenticationException.class) {

      }
      else if (exceptionClazz == NonceExpiredException.class) {

      }
      else if (exceptionClazz == PreAuthenticatedCredentialsNotFoundException.class) {

      }
      else if (exceptionClazz == ProviderNotFoundException.class) {

      }
      else if (exceptionClazz == RememberMeAuthenticationException.class) {

      }
      else if (exceptionClazz == SessionAuthenticationException.class) {

      }
   }
}

public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

   @Inject
   private UserDao userDao;

   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
      super.onAuthenticationSuccess(request, response, authentication);
      UserPersonalInfoBean activeUser = (UserPersonalInfoBean) authentication.getPrincipal();
      request.getSession().setAttribute("activeUser", activeUser);
      userDao.resetUserLoginFailedAttempts(activeUser.getUsername());
   }
}
  • 我找到的最后一种方法是实现spring-context ApplicationListener

@Named
public class BadCredentialsListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
   private static final long   serialVersionUID              = -404551400448242299L;

   private static final String MESSAGES_RESOURCE_BUNDLE_NAME = "msgs";
   private static final String ACCESS_DENIED_MESSAGE_KEY     = "accessDeniedMessage";
   private static final String BAD_CREDENTIALS_MESSAGE_KEY   = "badCredentialsMessage";

   @Override
   public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
      FacesContext fc = FacesContext.getCurrentInstance();
      ResourceBundle messages = fc.getApplication().getResourceBundle(fc, MESSAGES_RESOURCE_BUNDLE_NAME);
      fc.getExternalContext().getSessionMap().put(WebAttributes.AUTHENTICATION_EXCEPTION, null);
      fc.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, messages.getString(ACCESS_DENIED_MESSAGE_KEY), messages.getString(BAD_CREDENTIALS_MESSAGE_KEY)));
   }

}

我的问题终于来了。我是一名初级开发人员,并且无法找出/找出哪种方式有效/有效,简而言之是克服我使用的要求和技术的最佳方法(jsr330注入,jsf上下文等)。

我放弃了jsf PhaseListener解决方案,原因如上所述。实际上,spring-security访问和失败处理程序与PhaseListener类似,但效率更高,因为它们是在更具体的条件下调用的。必须根据其类型从异常中获取更具体的事件。但是,我同意在security-context.xml中定义它们会增加安全模块的可读性。听我的AbstractAuthenticationFailureEvent子课看起来很好。每个事件彼此垂直分离并且干净。此外,由于AuthenticationException的{​​{1}}和getExtraInformation方法已被弃用,我无法找到另一种方法来在getAuthentication中找到失败用户的用户名。

所以你明白了,我很困惑并且对你的评论持开放态度。

提前谢谢你, 此致

1 个答案:

答案 0 :(得分:0)

我认为决定是否使用一种选择取决于您的要求。

例如,使用处理程序的一个动机:处理程序获取请求和响应参数。因此,如果您想在某些情况下将用户重定向到某个页面(例如,如果帐户被锁定,并且您希望向他显示不同的HTML页面),则应使用处理程序。事件监听器不能(也不应该)重定向用户或更改流。他们只是听众......