我正在使用Spring Security 3.1.4,有以下要求:
- 监视身份验证是否成功或失败
- 如果成功,则将用户的一般信息放入会话属性中
- 如果结果是失败,则:
- 确定失败的原因(帐户被锁定、帐户过期、凭据过期、用户已禁用、登录失败尝试超过等)
- 为位于login.xhtml中的growl组件生成登录失败消息
- 针对失败事件采取特定的操作,例如在凭证错误时在数据库中增加登录失败尝试次数和/或重定向到重新定义凭证的页面
我已经进行了研究,并找到了三种解决方案:
- 实现
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;
}
}
- 另一种方法是自定义
AuthenticationFailureHandler
和AuthenticationSuccessHandler
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访问和失败处理程序类似于PhaseListeners,但更有效,因为它们在更具体的条件下被调用。必须从基于异常类型的特定事件中选择更具体的事件。然而,我应该同意,在security-context.xml中定义它们会增加安全模块的可读性。监听AbstractAuthenticationFailureEvent子类对我来说看起来非常好。每个事件都与其他事件垂直分离,很干净。此外,由于AuthenticationException的getExtraInformation和getAuthentication方法已过时,我找不到另一种方法来在AuthenticationFailureHandler.onAuthenticationFailure中获取失败用户的用户名。
所以,伙计们,正如你们所理解的那样,我非常困惑,期待你们的评论。
提前感谢您, 问候
AuthenticationFailureEvent
。 - Arun P JohnySystemAuthenticationFailureHandler
类,它扩展了ExceptionMappingAuthenticationFailureHandler
。我将其设置为spring-security登录的默认失败处理程序。您可以在此处查看我的一些代码。当过程失败时,我只需在handle
方法中获取凭据和AuthenticationException
。我将它们放入当前用户的会话中,jsf将在下一个视图中检索它们以显示屏幕上的适当消息。 - Aritz