为啥 Spring Security 使用默认的预认证检查?

Posted

技术标签:

【中文标题】为啥 Spring Security 使用默认的预认证检查?【英文标题】:Why does Spring Security use default pre authentication checks?为什么 Spring Security 使用默认的预认证检查? 【发布时间】:2016-05-13 00:33:40 【问题描述】:

我最近在我雇主的一个基于 Spring 的 Java 应用程序中实现了一些安全改进,并且我覆盖了 Spring Security 的 AbstractUserDetailsAuthenticationProvider 类,以便围绕用户身份验证进行一些额外的处理。在此过程中,我意识到DefaultPreAuthenticationChecks 内部类在身份验证提供程序运行通过验证密码的additionalAuthenticationChecks 方法之前执行用户帐户检查。如果用户被禁用、过期或锁定,则会抛出异常,从而将相关消息显示在屏幕上。对我来说,在成功验证密码之前检查用户帐户并提供此帐户的详细信息是一种公然的安全风险,因为它可能会暴露用户帐户是否存在。有谁知道 Spring Security 可能以这种方式做事的充分理由?显然,我可以通过创建自己的虚拟类来覆盖 DefaultPreAuthenticationChecks 类,方法是使用什么都不做的 check 方法,但遗憾的是,这必须首先完成。

提前致谢。

附:我在相关注释here 上发现了一个问题,但似乎没有人问这个问题为什么存在这个潜在的安全漏洞。

【问题讨论】:

可能是性能问题。如果用户被禁用,那么不要进行昂贵的 bcrypt 操作? 这很好,但我宁愿先检查一下,这样我们才能确保尝试访问帐户的人至少知道密码。然后我们知道它可能是授权帐户持有人是安全的,因此我们可以很高兴地告诉他们该帐户不幸被锁定或禁用。我想这是对速度的选择,而不是额外的安全性和处理。每次进行身份验证时,我都会选择后者! 【参考方案1】:

我想我参加聚会有点晚了,但如果有人仍然想知道,社区实际上有discussed this issue before

引自开发者

卢克·泰勒说:

事实并非如此。由您决定向您显示什么失败消息 登录失败时的用户——没有什么可以阻止你说 无论原因如何,“登录失败”。这也并不少见 应用程序通知用户他们的帐户已被锁定后 固定的登录尝试次数。所需的行为将取决于 您如何解释不同的状态标志(这不是 由框架严格定义)。例外情况也推动了 由 AuthenticationManager 生成事件,因此它们不是 必须在那里供用户消费。系统管理员可能想要 任何时候有人试图使用锁定或禁用 例如,不只是当他们使用正确的密码时。

也可以说是先查密码再开账号 锁定状态并仅在正确时显示“锁定”消息 给出密码也将允许暴力密码检查,甚至 帐户被锁定后。

所以我不同意这种行为是“不正确的”。我想可能是 不过,对于能够自定义事物很有用。也许我们应该有 方法

preAuthenticationChecks(UserDetails 用户)

postAuthentication(UserDetails 用户)

当不同的标志是 检查。

tl;dr:社区已经意识到这个问题,但他们不认为这是一个潜在的安全漏洞,而是他们认为这取决于“你如何解释不同的状态标志”,是的,很容易改变使用setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks)setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) 的默认行为

【讨论】:

【参考方案2】:

这绝对是为了性能优化。此外,在检查密码是否正确之前检查禁用或锁定的帐户会更安全,反之亦然。

正如 Neil 提到的,如果您之前进行过其他检查,则可以避免像 bcrypt 这样的昂贵操作。

让我们考虑第二点,即安全问题。考虑您的应用程序首先检查密码,如果密码有效,它会执行其他检查,例如帐户被锁定/禁用等。 潜在的黑客很快就会理解这种行为并使用暴力破解用户的密码,因为他知道只有在密码正确的情况下才会显示帐户锁定/禁用消息。

现在回到您的担忧,即使用这种方法存在暴露用户帐户是否存在的风险。首先,我认为破解密码的风险大于暴露该帐户是否存在的风险。其次,应用程序可以捕获这些检查(锁定/禁用)引发的异常并提供自定义消息。例如

catch(LockedException e)
  // log actual reason, so that it could be used for debugging purpose
  return "Invalid credentials"; // or throw BadCrentials exception

更多详情,请点击此处:https://github.com/spring-projects/spring-security/issues/798

【讨论】:

以上是关于为啥 Spring Security 使用默认的预认证检查?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Security 的预认证/单点登录

更改 Spring Security SAML2 登录的 URL

为啥 https 中使用 Spring Security 添加端口 80?

为啥spring-security-oauth oauth 2.0实现中需要scope参数

为啥 Spring Security 的 BindAuthenticator 需要用户读取权限?

为啥在 Spring Security 的 hasPermission 检查中使用“#post”而不是“post”