Spring Security:为什么抛出LockedException而不是BadCredentialsException?

IT小君   2023-09-16T19:43:11

使用Spring Security 4.0.2.RELEASE

对于使用spring-security框架进行基本用户身份验证,我实现了spring-security的DaoAuthenticationProvider

当用户尝试使用正确的用户名,错误的密码和用户的账户已被锁定时,我期望spring-security身份验证模块会抛出BadCredentialsException,但它抛出的是LockedException

我的问题是:

  1. 为什么spring-security在凭证(尤其是密码)不正确的情况下仍然处理用户进行进一步的身份验证?
  2. 即使用户的密码无效,是否在应用程序中显示消息“用户已被锁定”是好的做法?
  3. 如何处理生成/捕获无效密码和已锁定用户的BadCredentialsException

将不胜感激地接受任何帮助。身份验证提供程序的实现代码如下:

@Component("authenticationProvider")
public class LoginAuthenticationProvider extends DaoAuthenticationProvider {

    @Autowired
    UserDAO userDAO;

    @Autowired
    @Qualifier("userDetailsService")
    @Override
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        super.setUserDetailsService(userDetailsService);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        try {
            Authentication auth = super.authenticate(authentication);
            // 如果成功登录,就会到达这里,否则会抛出异常

            // 重置用户尝试次数
            userDAO.resetPasswordRetryAttempts(authentication.getName());

            return auth;
        } catch (BadCredentialsException ex) {
            // 无效的登录,更新用户尝试次数
            userDAO.updatePasswordRetryAttempts(authentication.getName(), PropertyUtils.getLoginAttemptsLimit());
            throw ex;
        } catch (LockedException ex) {
            // 该用户被锁定
            throw ex;
        } catch (AccountExpiredException ex) {
            // 该用户已过期
            throw ex;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw ex;
        }
    }

}
评论(2)
IT小君

你的问题是:

为什么Spring Security在验证密码时会抛出LockedException而不是BadCredentialsException?

这是因为Spring Security首先会检查账户是否存在且有效,然后再检查密码。

更具体地说,这是在AbstractUserDetailsAuthenticationProvider.authenticate方法中完成的。这个方法的工作方式可以简要描述如下:

user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
...
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
...
postAuthenticationChecks.check(user);
  • retrieveUser - 加载用户
  • preAuthenticationChecks.check(user); - DefaultPreAuthenticationChecks:检查是否被锁定...
  • additionalAuthenticationChecks - 检查密码
  • postAuthenticationChecks.check(user); - DefaultPostAuthenticationChecks:检查凭证是否过期

好处是,preAuthenticationCheckspostAuthenticationChecks是对UserDetailsChecker接口的引用,所以你可以进行更改。只需实现自己的两个UserDetailsChecker,一个是预检查的空实现,一个是后检查的实现,检查所有内容:

  • !user.isAccountNonLocked()
  • !user.isEnabled()
  • !user.isAccountNonExpired()
  • !user.isCredentialsNonExpired()
2023-09-16T19:43:28   回复
IT小君

当数据库中存在多个具有相同凭据(名称、电子邮件等)的用户时,我遇到了这个异常。反正这只是一个测试。在删除了这些重复项后,一切都好了。

2023-09-16T19:43:33   回复