(window.webpackJsonp=window.webpackJsonp||[]).push([[517],{945:function(e,r,s){"use strict";s.r(r);var a=s(56),t=Object(a.a)({},(function(){var e=this,r=e.$createElement,s=e._self._c||r;return s("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[s("h1",{attrs:{id:"密码存储"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#密码存储"}},[e._v("#")]),e._v(" 密码存储")]),e._v(" "),s("p",[e._v("Spring Security 的"),s("code",[e._v("PasswordEncoder")]),e._v("接口用于执行密码的单向转换,以允许安全地存储密码。给定"),s("code",[e._v("PasswordEncoder")]),e._v("是单向转换,当密码转换需要双向(即存储用于对数据库进行身份验证的凭据)时,并不打算这样做。通常"),s("code",[e._v("PasswordEncoder")]),e._v("用于存储需要与用户在身份验证时提供的密码进行比较的密码。")]),e._v(" "),s("h2",{attrs:{id:"密码存储历史"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#密码存储历史"}},[e._v("#")]),e._v(" 密码存储历史")]),e._v(" "),s("p",[e._v("多年来,存储密码的标准机制一直在发展。在开始的时候,密码是用纯文本格式存储的。这些密码被认为是安全的,因为数据存储中的密码被保存在访问它所需的凭据中。然而,恶意用户能够通过 SQL 注入等攻击,找到获取大量用户名和密码的“数据转储”的方法。随着越来越多的用户证书成为公共安全专家意识到,我们需要做更多的工作来保护用户的密码。")]),e._v(" "),s("p",[e._v("然后鼓励开发人员在通过 SHA-256 等单向散列运行密码后存储密码。当用户试图进行身份验证时,会将散列密码与他们键入的密码的散列进行比较。这意味着系统只需要存储密码的单向散列。如果发生了漏洞,那么只会公开密码的单向散列。由于散列是一种方式,并且在计算上很难猜测给定散列的密码,因此不值得花费精力来计算系统中的每个密码。为了击败这个新系统,恶意用户决定创建名为"),s("a",{attrs:{href:"https://en.wikipedia.org/wiki/Rainbow_table",target:"_blank",rel:"noopener noreferrer"}},[e._v("彩虹桌"),s("OutboundLink")],1),e._v("的查找表。他们不是每次都猜测每个密码,而是计算一次密码并将其存储在查找表中。")]),e._v(" "),s("p",[e._v("为了降低 Rainbow 表的有效性,鼓励开发人员使用盐渍密码。不再只使用密码作为散列函数的输入,而是为每个用户的密码生成随机字节(称为 salt)。SALT 和用户的密码将通过散列函数运行,该函数将生成一个唯一的散列。这种盐将与用户的密码一起以明文形式存储。然后,当用户试图进行身份验证时,散列密码将与存储的盐的散列和他们输入的密码进行比较。独特的盐意味着彩虹表格不再有效,因为每种盐和密码组合的散列值都不同。")]),e._v(" "),s("p",[e._v("在现代,我们意识到加密散列(如 SHA-256)不再安全。原因在于,有了现代硬件,我们每秒钟可以进行数十亿次散列计算。这意味着我们可以轻松地单独破解每个密码。")]),e._v(" "),s("p",[e._v("现在鼓励开发人员利用自适应单向功能来存储密码。具有自适应单向功能的密码的验证是有意的资源密集型的(如 CPU、内存等)。一个自适应的单向功能允许配置一个“工作因素”,可以随着硬件变得更好而增长。建议将“工作因子”调整为在系统上验证密码所需的时间约为 1 秒。这样做的代价是让攻击者很难破解密码,但也不会因为代价太高而给自己的系统带来过大的负担。 Spring 安全性已经尝试为“工作因素”提供了一个很好的起点,但是鼓励用户为自己的系统定制“工作因素”,因为系统之间的性能会有很大的差异。应该使用的自适应单向函数的示例包括"),s("a",{attrs:{href:"#authentication-password-storage-bcrypt"}},[e._v("bcrypt")]),e._v("、"),s("a",{attrs:{href:"#authentication-password-storage-pbkdf2"}},[e._v("PBKDF2")]),e._v("、"),s("a",{attrs:{href:"#authentication-password-storage-scrypt"}},[e._v("scrypt")]),e._v("和"),s("a",{attrs:{href:"#authentication-password-storage-argon2"}},[e._v("argon2")]),e._v("。")]),e._v(" "),s("p",[e._v("由于自适应单向函数是故意的资源密集型的,因此为每个请求验证用户名和密码将大大降低应用程序的性能。 Spring 安全性(或任何其他库)不能做任何事情来加速密码的验证,因为安全性是通过使验证资源密集型来获得的。鼓励用户将长期凭据(即用户名和密码)交换为短期凭据(即会话、OAuth 令牌等)。短期凭据可以被快速验证,而不会损失任何安全性。")]),e._v(" "),s("h2",{attrs:{id:"passwordencoder"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#passwordencoder"}},[e._v("#")]),e._v(" PasswordEncoder")]),e._v(" "),s("p",[e._v("在 Spring Security5.0 之前,默认的"),s("code",[e._v("PasswordEncoder")]),e._v("是"),s("code",[e._v("NoOpPasswordEncoder")]),e._v(",它需要纯文本密码。基于"),s("a",{attrs:{href:"#authentication-password-storage-history"}},[e._v("密码历史记录")]),e._v("部分,你可能认为默认的"),s("code",[e._v("PasswordEncoder")]),e._v("现在类似于"),s("code",[e._v("bcryptpasswordencoder")]),e._v("。然而,这忽略了三个现实世界中的问题:")]),e._v(" "),s("ul",[s("li",[s("p",[e._v("有许多应用程序使用旧的密码编码,无法轻松地进行迁移。")])]),e._v(" "),s("li",[s("p",[e._v("密码存储的最佳实践将再次改变。")])]),e._v(" "),s("li",[s("p",[e._v("作为一种框架 Spring,安全不能频繁地进行破坏更改")])])]),e._v(" "),s("p",[e._v("Spring 安全性引入了"),s("code",[e._v("DelegatingPasswordEncoder")]),e._v(",它通过以下方式解决了所有问题:")]),e._v(" "),s("ul",[s("li",[s("p",[e._v("确保使用当前的密码存储建议对密码进行编码")])]),e._v(" "),s("li",[s("p",[e._v("允许验证现代和遗留格式的密码。")])]),e._v(" "),s("li",[s("p",[e._v("允许在将来升级编码")])])]),e._v(" "),s("p",[e._v("你可以使用"),s("code",[e._v("PasswordEncoderFactories")]),e._v("轻松地构造"),s("code",[e._v("DelegatingPasswordEncoder")]),e._v("的实例。")]),e._v(" "),s("p",[e._v("例 1。创建默认的代理 PasswordEncoder")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("PasswordEncoder passwordEncoder =\n PasswordEncoderFactories.createDelegatingPasswordEncoder();\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()\n")])])]),s("p",[e._v("或者,你可以创建自己的自定义实例。例如:")]),e._v(" "),s("p",[e._v("例 2。创建自定义代理 PasswordEncoder")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('String idForEncode = "bcrypt";\nMap encoders = new HashMap<>();\nencoders.put(idForEncode, new BCryptPasswordEncoder());\nencoders.put("noop", NoOpPasswordEncoder.getInstance());\nencoders.put("pbkdf2", new Pbkdf2PasswordEncoder());\nencoders.put("scrypt", new SCryptPasswordEncoder());\nencoders.put("sha256", new StandardPasswordEncoder());\n\nPasswordEncoder passwordEncoder =\n new DelegatingPasswordEncoder(idForEncode, encoders);\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('val idForEncode = "bcrypt"\nval encoders: MutableMap = mutableMapOf()\nencoders[idForEncode] = BCryptPasswordEncoder()\nencoders["noop"] = NoOpPasswordEncoder.getInstance()\nencoders["pbkdf2"] = Pbkdf2PasswordEncoder()\nencoders["scrypt"] = SCryptPasswordEncoder()\nencoders["sha256"] = StandardPasswordEncoder()\n\nval passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)\n')])])]),s("h3",{attrs:{id:"密码存储格式"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#密码存储格式"}},[e._v("#")]),e._v(" 密码存储格式")]),e._v(" "),s("p",[e._v("密码的一般格式是:")]),e._v(" "),s("p",[e._v("例 3。passwordencoder 存储格式")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("{id}encodedPassword\n")])])]),s("p",[e._v("使得"),s("code",[e._v("id")]),e._v("是用于查找应该使用"),s("code",[e._v("PasswordEncoder")]),e._v("的标识符,而"),s("code",[e._v("encodedPassword")]),e._v("是所选"),s("code",[e._v("PasswordEncoder")]),e._v("的原始编码密码。"),s("code",[e._v("id")]),e._v("必须位于密码的开头,以"),s("code",[e._v("{")]),e._v("开头,以"),s("code",[e._v("}")]),e._v("结尾。如果不能找到"),s("code",[e._v("id")]),e._v(",则"),s("code",[e._v("id")]),e._v("将为空。例如,以下可能是使用不同的"),s("code",[e._v("id")]),e._v("编码的密码列表。所有的原始密码都是“密码”。")]),e._v(" "),s("p",[e._v("例 4。代理 PasswordEncoder 编码的密码示例")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG (1)\n{noop}password (2)\n{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc (3)\n{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= (4)\n{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 (5)\n")])])]),s("table",[s("thead",[s("tr",[s("th",[s("strong",[e._v("1")])]),e._v(" "),s("th",[e._v("第一个密码的"),s("code",[e._v("PasswordEncoder")]),e._v("ID 为"),s("code",[e._v("bcrypt")]),e._v(",编码密码为"),s("code",[e._v("$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG")]),e._v("。"),s("br"),e._v("匹配时,将委托给"),s("code",[e._v("BCryptPasswordEncoder")])])])]),e._v(" "),s("tbody",[s("tr",[s("td",[s("strong",[e._v("2")])]),e._v(" "),s("td",[e._v("第二个密码的"),s("code",[e._v("PasswordEncoder")]),e._v("ID 为"),s("code",[e._v("noop")]),e._v(",编码密码为"),s("code",[e._v("password")]),e._v("。")])]),e._v(" "),s("tr",[s("td",[s("strong",[e._v("3")])]),e._v(" "),s("td",[e._v("第三个密码的"),s("code",[e._v("PasswordEncoder")]),e._v("ID 为"),s("code",[e._v("pbkdf2")]),e._v(",编码密码为"),s("code",[e._v("5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc")]),e._v("。")])]),e._v(" "),s("tr",[s("td",[s("strong",[e._v("4")])]),e._v(" "),s("td",[e._v("第四个密码的"),s("code",[e._v("PasswordEncoder")]),e._v("ID 为"),s("code",[e._v("scrypt")]),e._v(",编码密码为"),s("code",[e._v("$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=")]),e._v(",当与之匹配时,它将委托给"),s("code",[e._v("ScryptPassWordEncoder")]),e._v("。")])]),e._v(" "),s("tr",[s("td",[s("strong",[e._v("5")])]),e._v(" "),s("td",[e._v("最终的密码将具有"),s("code",[e._v("PasswordEncoder")]),e._v("的 ID"),s("code",[e._v("sha256")]),e._v("和"),s("code",[e._v("97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0")]),e._v("的编码密码。"),s("br"),e._v("匹配时,它将委托给"),s("code",[e._v("StandardPasswordEncoder")]),e._v("。")])])])]),e._v(" "),s("table",[s("thead",[s("tr",[s("th"),e._v(" "),s("th",[e._v("一些用户可能担心存储格式是为潜在的黑客提供的。"),s("br"),e._v("这不是一个问题,因为密码的存储不依赖于算法是一个秘密。"),s("br"),e._v("此外,大多数格式都很容易被攻击者在没有前缀的情况下发现。"),s("br"),e._v("例如,bcrypt 密码通常以"),s("code",[e._v("$2a$")]),e._v("开头。")])])]),e._v(" "),s("tbody")]),e._v(" "),s("h3",{attrs:{id:"密码编码"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#密码编码"}},[e._v("#")]),e._v(" 密码编码")]),e._v(" "),s("p",[e._v("传递到构造函数的"),s("code",[e._v("idForEncode")]),e._v("确定将使用哪个"),s("code",[e._v("PasswordEncoder")]),e._v("来编码密码。在我们上面构造的"),s("code",[e._v("DelegatingPasswordEncoder")]),e._v("中,这意味着编码的结果"),s("code",[e._v("password")]),e._v("将被委托给"),s("code",[e._v("BCryptPasswordEncoder")]),e._v(",并以"),s("code",[e._v("{bcrypt}")]),e._v("作为前缀。最终的结果会是:")]),e._v(" "),s("p",[e._v("例 5。delegatingPassWordEncoder 编码示例")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG\n")])])]),s("h3",{attrs:{id:"密码匹配"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#密码匹配"}},[e._v("#")]),e._v(" 密码匹配")]),e._v(" "),s("p",[e._v("匹配是基于"),s("code",[e._v("{id}")]),e._v("和"),s("code",[e._v("id")]),e._v("到构造函数中提供的"),s("code",[e._v("PasswordEncoder")]),e._v("的映射完成的。我们在"),s("a",{attrs:{href:"#authentication-password-storage-dpe-format"}},[e._v("密码存储格式")]),e._v("中的示例提供了如何实现这一点的工作示例。默认情况下,使用密码调用"),s("code",[e._v("matches(CharSequence, String)")]),e._v("并调用未映射(包括空 ID)的"),s("code",[e._v("id")]),e._v("的结果将导致"),s("code",[e._v("IllegalArgumentException")]),e._v("。可以使用"),s("code",[e._v("DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)")]),e._v("定制此行为。")]),e._v(" "),s("p",[e._v("通过使用"),s("code",[e._v("id")]),e._v(",我们可以在任何密码编码上进行匹配,但是可以使用最现代的密码编码来编码密码。这一点很重要,因为与加密不同,密码散列的设计使得没有简单的方法来恢复明文。由于无法恢复明文,因此很难迁移密码。虽然迁移"),s("code",[e._v("NoOpPasswordEncoder")]),e._v("对用户来说很简单,但我们选择在默认情况下包含它,以使入门体验更简单。")]),e._v(" "),s("h3",{attrs:{id:"入门经验"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#入门经验"}},[e._v("#")]),e._v(" 入门经验")]),e._v(" "),s("p",[e._v("如果你正在组装一个演示或示例,那么花时间对用户的密码进行散列会有点麻烦。有方便的机制使这一点更容易,但这仍然不打算用于生产。")]),e._v(" "),s("p",[e._v("示例 6.与 DefaultPassWordEncoder 示例")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('User user = User.withDefaultPasswordEncoder()\n .username("user")\n .password("password")\n .roles("user")\n .build();\nSystem.out.println(user.getPassword());\n// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('val user = User.withDefaultPasswordEncoder()\n .username("user")\n .password("password")\n .roles("user")\n .build()\nprintln(user.password)\n// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG\n')])])]),s("p",[e._v("如果要创建多个用户,还可以重用构建器。")]),e._v(" "),s("p",[e._v("示例 7.WithDefaultPassWordEncoder 重用构建器")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('UserBuilder users = User.withDefaultPasswordEncoder();\nUser user = users\n .username("user")\n .password("password")\n .roles("USER")\n .build();\nUser admin = users\n .username("admin")\n .password("password")\n .roles("USER","ADMIN")\n .build();\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('val users = User.withDefaultPasswordEncoder()\nval user = users\n .username("user")\n .password("password")\n .roles("USER")\n .build()\nval admin = users\n .username("admin")\n .password("password")\n .roles("USER", "ADMIN")\n .build()\n')])])]),s("p",[e._v("这会对存储的密码进行散列,但这些密码仍会在内存和编译后的源代码中公开。因此,对于生产环境来说,它仍然不被认为是安全的。对于生产,你应该"),s("a",{attrs:{href:"#authentication-password-storage-boot-cli"}},[e._v("在外部散列密码")]),e._v("。")]),e._v(" "),s("h3",{attrs:{id:"用-spring-boot-cli-编码"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#用-spring-boot-cli-编码"}},[e._v("#")]),e._v(" 用 Spring boot cli 编码")]),e._v(" "),s("p",[e._v("正确编码密码的最简单方法是使用"),s("a",{attrs:{href:"https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-cli.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("Spring Boot CLI"),s("OutboundLink")],1),e._v("。")]),e._v(" "),s("p",[e._v("例如,下面将对"),s("code",[e._v("password")]),e._v("的密码进行编码,以便与"),s("a",{attrs:{href:"#authentication-password-storage-dpe"}},[e._v("PasswordEncoder")]),e._v("一起使用:")]),e._v(" "),s("p",[e._v("例 8。 Spring 启动 CLI EncodePassword 示例")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("spring encodepassword password\n{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6\n")])])]),s("h3",{attrs:{id:"故障排除"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#故障排除"}},[e._v("#")]),e._v(" 故障排除")]),e._v(" "),s("p",[e._v("当存储的密码之一没有"),s("a",{attrs:{href:"#authentication-password-storage-dpe-format"}},[e._v("密码存储格式")]),e._v("中所述的 ID 时,会发生以下错误。")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"\n\tat org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)\n\tat org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)\n')])])]),s("p",[e._v("解决该错误的最简单方法是切换到显式提供你的密码所使用的"),s("code",[e._v("PasswordEncoder")]),e._v("。解决此问题的最简单方法是弄清楚当前如何存储密码,并显式地提供正确的"),s("code",[e._v("PasswordEncoder")]),e._v("。")]),e._v(" "),s("p",[e._v("如果你正在从 Spring Security4.2.x 迁移,则可以通过[公开"),s("code",[e._v("NoOpPasswordEncoder")]),e._v(" Bean](# 身份验证-密码-存储-配置)来恢复到以前的行为。")]),e._v(" "),s("p",[e._v("或者,你可以在所有密码前加上正确的 ID,然后继续使用"),s("code",[e._v("DelegatingPasswordEncoder")]),e._v("。例如,如果你正在使用 bcrypt,那么你将从以下内容迁移密码:")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG\n")])])]),s("p",[e._v("to")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG\n")])])]),s("p",[e._v("对于映射的完整列表,请参考"),s("a",{attrs:{href:"https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/security/crypto/factory/PasswordEncoderFactories.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("PasswordEncoderFactories"),s("OutboundLink")],1),e._v("上的 爪哇doc。")]),e._v(" "),s("h2",{attrs:{id:"bcryptpasswordencoder"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#bcryptpasswordencoder"}},[e._v("#")]),e._v(" BCryptPasswordEncoder")]),e._v(" "),s("p",[s("code",[e._v("BCryptPasswordEncoder")]),e._v("实现使用广泛支持的"),s("a",{attrs:{href:"https://en.wikipedia.org/wiki/Bcrypt",target:"_blank",rel:"noopener noreferrer"}},[e._v("bcrypt"),s("OutboundLink")],1),e._v("算法来散列密码。为了使其更好地抵抗密码破解,bcrypt 故意放慢速度。像其他自适应单向功能一样,它应该调整为在系统上验证密码所需的时间约为 1 秒。"),s("code",[e._v("BCryptPasswordEncoder")]),e._v("的默认实现使用了在"),s("a",{attrs:{href:"https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html",target:"_blank",rel:"noopener noreferrer"}},[e._v("bcryptpasswordencoder"),s("OutboundLink")],1),e._v("的 爪哇doc 中提到的强度 10。我们鼓励你在自己的系统上调整和测试强度参数,这样验证密码大约需要 1 秒钟。")]),e._v(" "),s("p",[e._v("例 9。bcryptpasswordencoder")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with strength 16\nBCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);\nString result = encoder.encode("myPassword");\nassertTrue(encoder.matches("myPassword", result));\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with strength 16\nval encoder = BCryptPasswordEncoder(16)\nval result: String = encoder.encode("myPassword")\nassertTrue(encoder.matches("myPassword", result))\n')])])]),s("h2",{attrs:{id:"argon2passwordencoder"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#argon2passwordencoder"}},[e._v("#")]),e._v(" Argon2Passwordencoder")]),e._v(" "),s("p",[s("code",[e._v("Argon2PasswordEncoder")]),e._v("实现使用"),s("a",{attrs:{href:"https://en.wikipedia.org/wiki/Argon2",target:"_blank",rel:"noopener noreferrer"}},[e._v("Argon2"),s("OutboundLink")],1),e._v("算法来散列密码。Argon2 是"),s("a",{attrs:{href:"https://en.wikipedia.org/wiki/Password_Hashing_Competition",target:"_blank",rel:"noopener noreferrer"}},[e._v("密码散列竞赛"),s("OutboundLink")],1),e._v("的获胜者。为了击败定制硬件上的密码破解,Argon2 是一种故意缓慢的算法,需要大量内存。像其他自适应单向功能一样,它应该调整为在系统上验证密码所需的时间约为 1 秒。"),s("code",[e._v("Argon2PasswordEncoder")]),e._v("的当前实现需要 bouncycastle。")]),e._v(" "),s("p",[e._v("例 10。Argon2Passwordencoder")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with all the defaults\nArgon2PasswordEncoder encoder = new Argon2PasswordEncoder();\nString result = encoder.encode("myPassword");\nassertTrue(encoder.matches("myPassword", result));\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with all the defaults\nval encoder = Argon2PasswordEncoder()\nval result: String = encoder.encode("myPassword")\nassertTrue(encoder.matches("myPassword", result))\n')])])]),s("h2",{attrs:{id:"pbkdf2passwordencoder"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#pbkdf2passwordencoder"}},[e._v("#")]),e._v(" PBKDF2PASSWORDENCODER")]),e._v(" "),s("p",[s("code",[e._v("Pbkdf2PasswordEncoder")]),e._v("实现使用"),s("a",{attrs:{href:"https://en.wikipedia.org/wiki/PBKDF2",target:"_blank",rel:"noopener noreferrer"}},[e._v("PBKDF2"),s("OutboundLink")],1),e._v("算法来散列密码。为了击败密码破解,PBKDF2 是一种故意缓慢的算法。像其他自适应单向功能一样,它应该调整为在系统上验证密码所需的时间约为 1 秒。当需要 FIPS 认证时,该算法是一个很好的选择。")]),e._v(" "),s("p",[e._v("例 11。PBKDF2PASSWORDENCODER")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with all the defaults\nPbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();\nString result = encoder.encode("myPassword");\nassertTrue(encoder.matches("myPassword", result));\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with all the defaults\nval encoder = Pbkdf2PasswordEncoder()\nval result: String = encoder.encode("myPassword")\nassertTrue(encoder.matches("myPassword", result))\n')])])]),s("h2",{attrs:{id:"scryptpasswordencoder"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#scryptpasswordencoder"}},[e._v("#")]),e._v(" SCryptPasswordEncoder")]),e._v(" "),s("p",[s("code",[e._v("SCryptPasswordEncoder")]),e._v("实现使用"),s("a",{attrs:{href:"https://en.wikipedia.org/wiki/Scrypt",target:"_blank",rel:"noopener noreferrer"}},[e._v("scrypt"),s("OutboundLink")],1),e._v("算法来散列密码。为了击败定制硬件上的密码破解,Scrypt 是一种故意缓慢的算法,需要大量的内存。像其他自适应单向功能一样,它应该调整为在系统上验证密码所需的时间约为 1 秒。")]),e._v(" "),s("p",[e._v("例 12。ScryptPassWordEncoder")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with all the defaults\nSCryptPasswordEncoder encoder = new SCryptPasswordEncoder();\nString result = encoder.encode("myPassword");\nassertTrue(encoder.matches("myPassword", result));\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('// Create an encoder with all the defaults\nval encoder = SCryptPasswordEncoder()\nval result: String = encoder.encode("myPassword")\nassertTrue(encoder.matches("myPassword", result))\n')])])]),s("h2",{attrs:{id:"其他-passwordencoders"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#其他-passwordencoders"}},[e._v("#")]),e._v(" 其他 PasswordEncoders")]),e._v(" "),s("p",[e._v("还有相当数量的其他"),s("code",[e._v("PasswordEncoder")]),e._v("实现完全是为了向后兼容而存在的。它们都被弃用,以表明它们不再被认为是安全的。但是,由于很难迁移现有的遗留系统,因此没有删除它们的计划。")]),e._v(" "),s("h2",{attrs:{id:"密码存储配置"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#密码存储配置"}},[e._v("#")]),e._v(" 密码存储配置")]),e._v(" "),s("p",[e._v("Spring 安全性默认使用"),s("a",{attrs:{href:"#authentication-password-storage-dpe"}},[e._v("PasswordEncoder")]),e._v("。然而,这可以通过将"),s("code",[e._v("PasswordEncoder")]),e._v("公开为 Spring Bean 来定制。")]),e._v(" "),s("p",[e._v("如果从 Spring Security4.2.x 迁移,则可以通过公开"),s("code",[e._v("NoOpPasswordEncoder")]),e._v(" Bean 来恢复到以前的行为。")]),e._v(" "),s("table",[s("thead",[s("tr",[s("th"),e._v(" "),s("th",[e._v("恢复到"),s("code",[e._v("NoOpPasswordEncoder")]),e._v("不被认为是安全的。"),s("br"),e._v("你应该转而使用"),s("code",[e._v("DelegatingPasswordEncoder")]),e._v("来支持安全的密码编码。")])])]),e._v(" "),s("tbody")]),e._v(" "),s("p",[e._v("例 13。NoopPassWordEncoder")]),e._v(" "),s("p",[e._v("爪哇")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Bean\npublic static PasswordEncoder passwordEncoder() {\n return NoOpPasswordEncoder.getInstance();\n}\n")])])]),s("p",[e._v("XML")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("@Bean\nfun passwordEncoder(): PasswordEncoder {\n return NoOpPasswordEncoder.getInstance();\n}\n")])])]),s("table",[s("thead",[s("tr",[s("th"),e._v(" "),s("th",[e._v("XML 配置要求"),s("code",[e._v("NoOpPasswordEncoder")]),e._v(" Bean 名称为"),s("code",[e._v("passwordEncoder")]),e._v("。")])])]),e._v(" "),s("tbody")]),e._v(" "),s("h2",{attrs:{id:"更改密码配置"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#更改密码配置"}},[e._v("#")]),e._v(" 更改密码配置")]),e._v(" "),s("p",[e._v("大多数允许用户指定密码的应用程序也需要更新该密码的功能。")]),e._v(" "),s("p",[s("a",{attrs:{href:"https://w3c.github.io/webappsec-change-password-url/",target:"_blank",rel:"noopener noreferrer"}},[e._v("一个众所周知的更改密码的 URL"),s("OutboundLink")],1),e._v("表示一种机制,通过这种机制,密码管理器可以发现给定应用程序的密码更新端点。")]),e._v(" "),s("p",[e._v("你可以配置 Spring 安全性以提供此发现端点。例如,如果应用程序中的更改密码端点是"),s("code",[e._v("/change-password")]),e._v(",那么你可以这样配置 Spring 安全性:")]),e._v(" "),s("p",[e._v("例 14。默认更改密码端点")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("http\n .passwordManagement(Customizer.withDefaults())\n")])])]),s("p",[e._v("XML")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("\n")])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v("http {\n passwordManagement { }\n}\n")])])]),s("p",[e._v("然后,当密码管理器导航到"),s("code",[e._v("/.well-known/change-password")]),e._v("时, Spring 安全性将重定向你的端点,"),s("code",[e._v("/change-password")]),e._v("。")]),e._v(" "),s("p",[e._v("或者,如果你的端点不是"),s("code",[e._v("/change-password")]),e._v(",也可以这样指定:")]),e._v(" "),s("p",[e._v("例 15。更改密码端点")]),e._v(" "),s("p",[e._v("Java")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('http\n .passwordManagement((management) -> management\n .changePasswordPage("/update-password")\n )\n')])])]),s("p",[e._v("XML")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('\n')])])]),s("p",[e._v("Kotlin")]),e._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[e._v('http {\n passwordManagement {\n changePasswordPage = "/update-password"\n }\n}\n')])])]),s("p",[e._v("通过上述配置,当密码管理器导航到"),s("code",[e._v("/.well-known/change-password")]),e._v("时, Spring Security 将重定向到"),s("code",[e._v("/update-password")]),e._v("。")])])}),[],!1,null,null,null);r.default=t.exports}}]);