learn.lianglianglee.com/专栏/Spring Security 详解与实操/04 密码安全:Spring Security 中包含哪些加解密技术?.md.html
2022-08-14 03:40:33 +08:00

455 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>04 密码安全Spring Security 中包含哪些加解密技术?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/Spring Security 详解与实操/00 开篇词 Spring Security为你的应用安全与职业之路保驾护航.md.html">00 开篇词 Spring Security为你的应用安全与职业之路保驾护航</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/01 顶级框架Spring Security 是一款什么样的安全性框架?.md.html">01 顶级框架Spring Security 是一款什么样的安全性框架?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/02 用户认证:如何使用 Spring Security 构建用户认证体系?.md.html">02 用户认证:如何使用 Spring Security 构建用户认证体系?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/03 认证体系:如何深入理解 Spring Security 用户认证机制?.md.html">03 认证体系:如何深入理解 Spring Security 用户认证机制?</a>
</li>
<li>
<a class="current-tab" href="/专栏/Spring Security 详解与实操/04 密码安全Spring Security 中包含哪些加解密技术?.md.html">04 密码安全Spring Security 中包含哪些加解密技术?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/05 访问授权:如何对请求的安全访问过程进行有效配置?.md.html">05 访问授权:如何对请求的安全访问过程进行有效配置?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/06 权限管理:如何剖析 Spring Security 的授权原理?.md.html">06 权限管理:如何剖析 Spring Security 的授权原理?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/07 案例实战:使用 Spring Security 基础功能保护 Web 应用.md.html">07 案例实战:使用 Spring Security 基础功能保护 Web 应用</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/08 管道过滤:如何基于 Spring Security 过滤器扩展安全性?.md.html">08 管道过滤:如何基于 Spring Security 过滤器扩展安全性?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/09 攻击应对:如何实现 CSRF 保护和跨域 CORS.md.html">09 攻击应对:如何实现 CSRF 保护和跨域 CORS</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/10 全局方法:如何确保方法级别的安全访问?.md.html">10 全局方法:如何确保方法级别的安全访问?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/11 案例实战:使用 Spring Security 高级主题保护 Web 应用.md.html">11 案例实战:使用 Spring Security 高级主题保护 Web 应用</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/12 开放协议OAuth2 协议解决的是什么问题?.md.html">12 开放协议OAuth2 协议解决的是什么问题?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/13 授权体系:如何构建 OAuth2 授权服务器?.md.html">13 授权体系:如何构建 OAuth2 授权服务器?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/14 资源保护:如何基于 OAuth2 协议配置授权过程?.md.html">14 资源保护:如何基于 OAuth2 协议配置授权过程?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/15 令牌扩展:如何使用 JWT 实现定制化 Token.md.html">15 令牌扩展:如何使用 JWT 实现定制化 Token</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/16 案例实战:基于 Spring Security 和 Spring Cloud 构建微服务安全架构.md.html">16 案例实战:基于 Spring Security 和 Spring Cloud 构建微服务安全架构</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/17 案例实战:基于 Spring Security 和 OAuth2 实现单点登录.md.html">17 案例实战:基于 Spring Security 和 OAuth2 实现单点登录</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/18 技术趋势:如何为 Spring Security 添加响应式编程特性?.md.html">18 技术趋势:如何为 Spring Security 添加响应式编程特性?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/19 测试驱动:如何基于 Spring Security 测试系统安全性?.md.html">19 测试驱动:如何基于 Spring Security 测试系统安全性?</a>
</li>
<li>
<a href="/专栏/Spring Security 详解与实操/20 结束语 以终为始Spring Security 的学习总结.md.html">20 结束语 以终为始Spring Security 的学习总结</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>04 密码安全Spring Security 中包含哪些加解密技术?</h1>
<p>通过前面两讲内容的学习,相信你已经掌握了 Spring Security 中的用户认证体系。用户认证的过程通常涉及密码的校验因此密码的安全性也是我们需要考虑的一个核心问题。Spring Security 作为一款功能完备的安全性框架,一方面提供了<strong>用于完成认证操作的 PasswordEncoder 组件</strong>,另一方面也包含一个独立而完整的<strong>加密模块</strong>,方便在应用程序中单独使用。</p>
<h3>PasswordEncoder</h3>
<p>我们先来回顾一下整个用户认证流程。在 AuthenticationProvider 中,我们需要使用 PasswordEncoder 组件验证密码的正确性,如下图所示:</p>
<p><img src="assets/Cgp9HWC5_4iAIGeQAABZX7ZLFlk655.png" alt="Drawing 0.png" /></p>
<p>PasswordEncoder 组件与认证流程之间的关系</p>
<p>在“用户认证:如何使用 Spring Security 构建用户认证体系?”一讲中我们也介绍了基于数据库的用户信息存储方案:</p>
<pre><code>@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(&quot;select username, password, enabled from Users &quot; + &quot;where username=?&quot;)
.authoritiesByUsernameQuery(&quot;select username, authority from UserAuthorities &quot; + &quot;where username=?&quot;)
.passwordEncoder(new BCryptPasswordEncoder());
}
</code></pre>
<p>请注意,在上述方法中,我们通过 jdbcAuthentication() 方法验证用户信息时一定要<strong>集成加密机制</strong>,也就是使用 passwordEncoder() 方法嵌入一个 PasswordEncoder 接口的实现类。</p>
<h4>PasswordEncoder 接口</h4>
<p>在 Spring Security 中PasswordEncoder 接口代表的是一种密码编码器,其核心作用在于<strong>指定密码的具体加密方式</strong>以及如何将一段给定的加密字符串与明文之间完成匹配校验。PasswordEncoder 接口定义如下:</p>
<pre><code>public interface PasswordEncoder {
//对原始密码进行编码
String encode(CharSequence rawPassword);
//对提交的原始密码与库中存储的加密密码进行比对
boolean matches(CharSequence rawPassword, String encodedPassword);
//判断加密密码是否需要再次进行加密,默认返回 false
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
</code></pre>
<p>Spring Security 内置了一大批 PasswordEncoder 接口的实现类,如下所示:</p>
<p><img src="assets/CioPOWC5_5KAdkT3AAQNHWtae5I850.png" alt="Drawing 1.png" /></p>
<p>Spring Security 中的 PasswordEncoder 实现类</p>
<p>我们对上图中比较常见的几个 PasswordEncoder 接口展开叙述。</p>
<ul>
<li>NoOpPasswordEncoder以明文形式保留密码不对密码进行编码。这种 PasswordEncoder 通常只用于演示,不应该用于生产环境。</li>
<li>StandardPasswordEncoder使用 SHA-256 算法对密码执行哈希操作。</li>
<li>BCryptPasswordEncoder使用 bcrypt 强哈希算法对密码执行哈希操作。</li>
<li>Pbkdf2PasswordEncoder使用 PBKDF2 算法对密码执行哈希操作。</li>
</ul>
<p>下面我们以 BCryptPasswordEncoder 为例,看一下它的 encode 方法,如下所示:</p>
<pre><code>public String encode(CharSequence rawPassword) {
String salt;
if (random != null) {
salt = BCrypt.gensalt(version.getVersion(), strength, random);
} else {
salt = BCrypt.gensalt(version.getVersion(), strength);
}
return BCrypt.hashpw(rawPassword.toString(), salt);
}
</code></pre>
<p>可以看到,上述 encode 方法执行了两个步骤,首先使用 Spring Security 提供的 BCrypt 工具类生成盐Salt然后根据盐和明文密码生成最终的密文密码。这里有必要对加盐的概念做一些展开所谓加盐就是在初始化明文数据时由系统自动往这个明文里添加一些附加数据然后散列。引入加盐机制是为了<strong>进一步保证加密数据的安全性</strong>,单向散列加密以及加盐思想也被广泛应用于系统登录过程中的密码生成和校验。</p>
<p>同样,在 Pbkdf2PasswordEncoder 中,也是通过对密码加盐之后进行哈希,然后将结果作为盐再与密码进行哈希,多次重复此过程,生成最终的密文。</p>
<p>介绍完 PasswordEncoder 的基本结构,我们继续来看它的应用方式。如果我们想在应用程序中使用某一个 PasswordEncoder 实现类,通常只需要通过它的构造函数创建一个实例,例如:</p>
<pre><code>PasswordEncoder p = new StandardPasswordEncoder();
PasswordEncoder p = new StandardPasswordEncoder(&quot;secret&quot;);
PasswordEncoder p = new SCryptPasswordEncoder();
PasswordEncoder p = new SCryptPasswordEncoder(16384, 8, 1, 32, 64);
</code></pre>
<p>而如果想要使用 NoOpPasswordEncoder除了构造函数之外还可以通过它的 getInstance() 方法来获取静态实例,如下所示:</p>
<pre><code>PasswordEncoder p = NoOpPasswordEncoder.getInstance()
</code></pre>
<h4>自定义 PasswordEncoder</h4>
<p>尽管 Spring Security 已经为我们提供了丰富的 PasswordEncoder但你也可以通过实现这个接口来设计满足自身需求的任意一种密码编解码和验证机制。例如我们可以编写如下所示的一个 PlainTextPasswordEncoder</p>
<pre><code>public class PlainTextPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.equals(encodedPassword);
}
}
</code></pre>
<p>PlainTextPasswordEncoder 的功能与 NoOpPasswordEncoder 类似,没有对明文进行任何处理。如果你想使用某种算法集成 PasswordEncoder就可以实现类似如下所示的 Sha512PasswordEncoder这里使用了 SHA-512 作为加解密算法:</p>
<pre><code>public class Sha512PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return hashWithSHA512(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String hashedPassword = encode(rawPassword);
return encodedPassword.equals(hashedPassword);
}
private String hashWithSHA512(String input) {
StringBuilder result = new StringBuilder();
try {
MessageDigest md = MessageDigest.getInstance(&quot;SHA-512&quot;);
byte [] digested = md.digest(input.getBytes());
for (int i = 0; i &lt; digested.length; i++) {
result.append(Integer.toHexString(0xFF &amp; digested[i]));
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(&quot;Bad algorithm&quot;);
}
return result.toString();
}
}
</code></pre>
<p>上述代码中hashWithSHA512() 方法就使用了前面提到的<strong>单向散列加密算法</strong>来生成消息摘要Message Digest其主要特点在于<strong>单向不可逆和密文长度固定</strong>。同时也具备“碰撞”少的优点即明文的微小差异就会导致所生成密文完全不同。SHASecure Hash Algorithm以及MD5Message Digest 5都是常见的单向散列加密算法在 JDK 自带的 MessageDigest 类中已经包含了默认实现,我们直接调用方法即可。</p>
<h4>代理式 DelegatingPasswordEncoder</h4>
<p>在前面的讨论中,我们都基于一个假设,即在对密码进行加解密过程中,只会使用到一个 PasswordEncoder如果这个 PasswordEncoder 不满足我们的需求,那么就需要替换成另一个 PasswordEncoder。这就引出了一个问题如何优雅地应对这种变化呢</p>
<p>在普通的业务系统中,由于业务系统也在不断地变化,替换一个组件可能并没有很高的成本。但对于 Spring Security 这种成熟的开发框架而言,在设计和实现上不能经常发生变化。因此,在新/旧 PasswordEncoder 的兼容性以及框架自身的稳健性和可变性之间需要保持一种平衡。为了实现这种平衡性Spring Security 提供了 DelegatingPasswordEncoder。</p>
<p>虽然 DelegatingPasswordEncoder 也实现了 PasswordEncoder 接口但事实上它更多扮演了一种代理组件的角色这点从命名上也可以看出来。DelegatingPasswordEncoder 将具体编码的实现根据要求代理给不同的算法,以此实现不同编码算法之间的兼容并协调变化,如下图所示:</p>
<p><img src="assets/Cgp9HWC5_6aACid_AACGDa8mfic454.png" alt="Drawing 2.png" /></p>
<p>DelegatingPasswordEncoder 的代理作用示意图</p>
<p>下面我们来看一下 DelegatingPasswordEncoder 类的构造函数,如下所示:</p>
<pre><code>public DelegatingPasswordEncoder(String idForEncode,
Map&lt;String, PasswordEncoder&gt; idToPasswordEncoder) {
if (idForEncode == null) {
throw new IllegalArgumentException(&quot;idForEncode cannot be null&quot;);
}
if (!idToPasswordEncoder.containsKey(idForEncode)) {
throw new IllegalArgumentException(&quot;idForEncode &quot; + idForEncode + &quot;is not found in idToPasswordEncoder &quot; + idToPasswordEncoder);
}
for (String id : idToPasswordEncoder.keySet()) {
if (id == null) {
continue;
}
if (id.contains(PREFIX)) {
throw new IllegalArgumentException(&quot;id &quot; + id + &quot; cannot contain &quot; + PREFIX);
}
if (id.contains(SUFFIX)) {
throw new IllegalArgumentException(&quot;id &quot; + id + &quot; cannot contain &quot; + SUFFIX);
}
}
this.idForEncode = idForEncode;
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
this.idToPasswordEncoder = new HashMap&lt;&gt;(idToPasswordEncoder);
}
</code></pre>
<p>该构造函数中的 idForEncode 参数决定 PasswordEncoder 的类型,而 idToPasswordEncoder 参数决定判断匹配时兼容的类型。显然idToPasswordEncoder 必须包含对应的 idForEncode。</p>
<p>我们再来看这个构造函数的调用入口。在 Spring Security 中,存在一个创建 PasswordEncoder 的工厂类 PasswordEncoderFactories如下所示</p>
<pre><code>public class PasswordEncoderFactories {
@SuppressWarnings(&quot;deprecation&quot;)
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = &quot;bcrypt&quot;;
Map&lt;String, PasswordEncoder&gt; encoders = new HashMap&lt;&gt;();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put(&quot;ldap&quot;, new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put(&quot;MD4&quot;, new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put(&quot;MD5&quot;, new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(&quot;MD5&quot;));
encoders.put(&quot;noop&quot;, org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put(&quot;pbkdf2&quot;, new Pbkdf2PasswordEncoder());
encoders.put(&quot;scrypt&quot;, new SCryptPasswordEncoder());
encoders.put(&quot;SHA-1&quot;, new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(&quot;SHA-1&quot;));
encoders.put(&quot;SHA-256&quot;, new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(&quot;SHA-256&quot;));
encoders.put(&quot;sha256&quot;, new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put(&quot;argon2&quot;, new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
private PasswordEncoderFactories() {}
}
</code></pre>
<p>可以看到,在这个工厂类中初始化了一个包含所有 Spring Security 中支持 PasswordEncoder 的 Map。而且我们也明确了框架默认使用的就是 key 为“bcrypt”的 BCryptPasswordEncoder。</p>
<p>通常,我们可以通过以下方法来使用这个 PasswordEncoderFactories 类:</p>
<pre><code>PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
</code></pre>
<p>另一方面PasswordEncoderFactories 的实现方法为我们自定义 DelegatingPasswordEncoder 提供了一种途径,我们也可以根据需要创建符合自己需求的 DelegatingPasswordEncoder如下所示</p>
<pre><code>String idForEncode = &quot;bcrypt&quot;;
Map encoders = new HashMap&lt;&gt;();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put(&quot;noop&quot;, NoOpPasswordEncoder.getInstance());
encoders.put(&quot;pbkdf2&quot;, new Pbkdf2PasswordEncoder());
encoders.put(&quot;scrypt&quot;, new SCryptPasswordEncoder());
encoders.put(&quot;sha256&quot;, new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
</code></pre>
<p>请注意,在 Spring Security 中,密码的标准存储格式是这样的:</p>
<pre><code>{id}encodedPassword
</code></pre>
<p>这里的 id 就是 PasswordEncoder 的种类,也就是前面提到的 idForEncode 参数。假设密码原文为“password”经过 BCryptPasswordEncoder 进行加密之后的密文就变成了这样一个字符串:</p>
<pre><code>$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
</code></pre>
<p>最终存储在数据库中的密文应该是这样的:</p>
<pre><code>{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
</code></pre>
<p>以上实现过程可以通过查阅 DelegatingPasswordEncoder 的 encode() 方法得到验证:</p>
<pre><code>@Override
public String encode(CharSequence rawPassword) {
return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
}
</code></pre>
<p>我们继续来看 DelegatingPasswordEncoder 的 matcher 方法,如下所示:</p>
<pre><code>@Override
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null &amp;&amp; prefixEncodedPassword == null) {
return true;
}
//取出 PasswordEncoder 的 id
String id = extractId(prefixEncodedPassword);
//根据 PasswordEncoder 的 id 获取对应的 PasswordEncoder
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
//如果找不到对应的 PasswordEncoder则使用默认 PasswordEncoder 进行匹配判断
if (delegate == null) {
return this.defaultPasswordEncoderForMatches
.matches(rawPassword, prefixEncodedPassword);
}
//从存储的密码字符串中抽取密文,去掉 id
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
//使用对应 PasswordEncoder 针对密文进行匹配判断
return delegate.matches(rawPassword, encodedPassword);
}
</code></pre>
<p>上述方法的流程还是很明确的,至此,我们对 DelegatingPasswordEncoder 的实现原理就讲清楚了,也进一步理解了 PasswordEncoder 的使用过程。</p>
<h3>Spring Security 加密模块</h3>
<p>正如我们在开头介绍的,使用 Spring Security 时通常涉及用户认证的部分会用到加解密技术。但就应用场景而言加解密技术是一种通用的基础设施类技术不仅可以用于用户认证也可以用于其他任何涉及敏感数据处理的场景。因此Spring Security 也充分考虑到了这种需求专门提供了一个加密模式Spring Security Crypto ModuleSSCM</p>
<p>请注意,尽管 PasswordEncoder 也属于这个模块的一部分,但这个模块本身是高度独立的,我们可以脱离于用户认证流程来使用这个模块。</p>
<p>Spring Security 加密模块的核心功能有两部分首先就是加解密器Encryptors典型的使用方式如下</p>
<pre><code>BytesEncryptor e = Encryptors.standard(password, salt);
</code></pre>
<p>上述方法使用了标准的 256 位 AES 算法对输入的 password 字段进行加密,返回的是一个 BytesEncryptor。同时我们也看到这里需要输入一个代表盐值的 salt 字段,而这个 salt 值的获取就可以用到 Spring Security 加密模块的另一个功能——键生成器Key Generators使用方式如下所示</p>
<pre><code>String salt = KeyGenerators.string().generateKey();
</code></pre>
<p>上述键生成器会创建一个 8 字节的密钥,并将其编码为十六进制字符串。</p>
<p>如果将加解密器和键生成器结合起来,我们就可以实现通用的加解密机制,如下所示:</p>
<pre><code>String salt = KeyGenerators.string().generateKey();
String password = &quot;secret&quot;;
String valueToEncrypt = &quot;HELLO&quot;;
BytesEncryptor e = Encryptors.standard(password, salt);
byte [] encrypted = e.encrypt(valueToEncrypt.getBytes());
byte [] decrypted = e.decrypt(encrypted);
</code></pre>
<p>在日常开发过程中,你可以根据需要调整上述代码并嵌入到我们的系统中。</p>
<h3>小结与预告</h3>
<p>对于一个 Web 应用程序而言一旦需要实现用户认证势必涉及用户密码等敏感信息的加密。为此Spring Security 专门提供了 PasswordEncoder 组件对密码进行加解密。Spring Security 内置了一批即插即用的 PasswordEncoder并通过代理机制完成了各个组件的版本兼容和统一管理。这种设计思想也值得我们学习和借鉴。当然作为一款通用的安全性开发框架Spring Security 也提供了一个高度独立的加密模块应对日常开发需求。</p>
<p>本讲内容总结如下:</p>
<p><img src="assets/CioPOWC5_7OAC11AAABs6h6B9l8729.png" alt="Drawing 3.png" /></p>
<p>这里给你留一道思考题:你能描述 DelegatingPasswordEncoder 所起到的代理作用吗?欢迎在留言区和我分享你的思考。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Spring Security 详解与实操/03 认证体系:如何深入理解 Spring Security 用户认证机制?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Spring Security 详解与实操/05 访问授权:如何对请求的安全访问过程进行有效配置?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099753cb9883d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>