SpringBoot3整合SpringSecurity6(四)添加用户、密码加密
<h3 id="写在前面">写在前面</h3><p>还记得在之前的文章中,我们在<code>user</code>表中手动插入了3条数据吗?</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758026-1915271397.png" alt="插入数据" loading="lazy"></p>
<p>当时,大家就会有疑问。这一串密码是怎么来的呢,我们为啥要对密码进行加密?</p>
<p>带着这些疑问,我们继续上路。我们在开发一个应用系统,肯定是少不了用户注册功能的。说到注册,无非就是往<code>user</code>用户信息表中插入一条数据。</p>
<p>这条数据包含用户名 、密码 等字段,而往往 密码这个字段不可能将123456直接存储的,而是通过各种加密手段处理。最终如上图所示,</p>
<p>即使数据库因各种原因泄露,法外狂徒张三也不知道用户的密码,这让系统又加了一把锁。</p>
<h3 id="一密码明文存储的危害">一、密码明文存储的危害</h3>
<p>不知道大家前段时间有没有听到这么一个新闻。爱尔兰数据保护委员会(DPC)对Meta(前Facebook) 处以9100万欧元(7.14亿元人民币)的罚款,</p>
<p>原因正是因为Meta在未加保护或加密的情况下<strong>以明文形式存储用户密码</strong>。</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758126-516248359.png" alt="Meta明文存储密码被罚款" loading="lazy"></p>
<p>从Meta事件再次证明了,即使在大的企业,也难免会有所疏漏,虽然事件中并没有提到泄露信息,但一旦泄露,后果真的不可想象。</p>
<p>密码明文存储就像是你把家里的钥匙挂在门上,任何人都可以拿走钥匙,进入你的家;你的日记本没有上锁,任何人都可以打开看;</p>
<p>安全无小事,安全无小事,安全无小事。</p>
<h3 id="二加密方式及实现">二、加密方式及实现</h3>
<h4 id="21-对称加密">2.1 对称加密</h4>
<p>① <strong>简介</strong></p>
<p>就像你和你的好朋友之间有一个只有你们俩知道的秘密暗号。你们用这个暗号来传递信息,别人就算看到了也看不懂。</p>
<p>② <strong>常见算法</strong></p>
<ul>
<li>AES(高级加密标准)</li>
<li>DES(数据加密标准)</li>
</ul>
<p>②<strong>实现</strong></p>
<p>首先,你和你的朋友要商量好一个“暗号”(密钥)。然后,你把想说的话(明文)转换成只有你们俩能懂的暗号(密文)。你的朋友收到后,再用同样的“暗号”把密文变回原话。</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075757968-195611672.png" alt="对称加密" loading="lazy"></p>
<h4 id="22-非对称加密">2.2 非对称加密</h4>
<p>① <strong>简介</strong></p>
<p>你有一个带锁的信箱,你把钥匙分成两把,一把公钥(可以给别人),一把私钥(自己保管)。别人用公钥把信锁上,只有你能用私钥打开</p>
<p>② <strong>常见算法</strong></p>
<ul>
<li><strong>RSA算法</strong></li>
</ul>
<p>③ <strong>实现</strong></p>
<p>你生成一对钥匙,公钥和私钥。别人用你的公钥加密信息,然后发给你。你收到后,用自己的私钥解密。</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075757999-1998640215.png" alt="非对称加密" loading="lazy"></p>
<h4 id="23-哈希加密">2.3 哈希加密</h4>
<p>① <strong>简介</strong></p>
<p>像是把信息扔进一个特殊的搅拌机,出来的是一个固定大小的“信息指纹”。这个“指纹”不能还原成原来的信息。</p>
<p>② <strong>常见算法</strong></p>
<ul>
<li><code>MD5</code></li>
<li><code>SHA-1</code></li>
<li><code>SHA-256</code></li>
<li><code>SHA-512</code></li>
</ul>
<p>③ <strong>实现</strong></p>
<p>把信息(明文)通过一个哈希函数处理,生成一个哈希值。这个哈希值用来验证信息是否被篡改。</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758105-924865520.png" alt="哈希加密" loading="lazy"></p>
<ul>
<li>
<p>选择一个哈希算法(如<code>SHA-256</code>)。</p>
</li>
<li>
<p>把明文通过哈希算法处理,生成哈希值。</p>
</li>
<li>
<p>存储或传输哈希值。</p>
</li>
<li>
<p>接收者收到信息后,再次计算哈希值,与收到的哈希值对比,验证信息完整性。</p>
</li>
</ul>
<p>回到<code>Spring Sesurity</code>中,框架提供了<code>PasswordEncoder</code> 接口来实现密码加密。通过不同的实现,来配置不同<code>Hash</code>算法(<code>MD5</code>、<code>SHA-256</code>、<code>SHA-512</code>等)</p>
<p>但是仅仅通过上面简单的加密依然不能防止恶意用户的攻击,为什么呢?</p>
<p>恶意用户会预先计算很多密码的加密结果,保存到“表”里。这里的“表”指的就我们常说的<strong>彩虹表</strong>。</p>
<p>当想要破解某个密码时,就会用这个密码去“表”里查找对应的加密结果,如果找到了,就知道这个密码是什么了。</p>
<p>那么<code>SpringSecurity</code>怎么才能防止彩虹表攻击呢?</p>
<p>常用的就是使用<strong>盐(Salt)</strong></p>
<p>顾名思义,盐就是给密码加点“料”,每次加密时都加点不同的“料”,这样即使两个用户的密码相同,加密后的结果也会因为盐的不同而不同。</p>
<p><code>Spring Security</code>可以通过配置密码编码器(如<code>BCryptPasswordEncoder</code>)来自动为每个密码生成一个唯一的盐来保证安全。</p>
<h3 id="三密码加密测试">三、密码加密测试</h3>
<p>上面说了一堆理论知识,接下来我们来测试下通过<code>BCryptPasswordEncoder</code> 来加密</p>
<pre><code class="language-java">@Test
public void testPasswordEncoder(){
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//明文都是xiezhr,但是每次生成的密文是不一致的
String ret1 = encoder.encode("xiezhr");
String ret2 = encoder.encode("xiezhr");
System.out.println("第一次生成密码:"+ret1);
System.out.println("第二次生成密码:"+ret2);
Assert.isTrue(encoder.matches("xiezhr", ret1), "密码不一致");
Assert.isTrue(encoder.matches("xiezhr", ret2), "密码不一致");
}
//输出
第一次生成密码:$2a$10$96WLiLGkHOcxYNw9wWHDeuXYVznW30S7F5u4Stib71gFp3Mq/mENa
第二次生成密码:$2a$10$f1JowhAXqYI1UBF4jRY5AeziL/83NRzWfvUtxoXzgW0xUQ/uLLUWK
//这里并没有抛出密码不一致的异常
</code></pre>
<p>从上面测试中,我们可以看出,相同明文"xiezhr" 通过<code>BCrypt</code>算法加密后,每次生成的密文都是不同的。</p>
<p>再通过<code>matches</code> 来验证两次生成的密文,都与“xiezhr” 匹配</p>
<h3 id="四添加新用户">四、添加新用户</h3>
<p>在前面的学习中,我们知道了怎么进行密码加密,接下来,该轮到实操了。</p>
<p>我们要实现将一个用户添加到数据库中,并且密码进行加密。</p>
<h4 id="41-替换默认密码加密方式">4.1 替换默认密码加密方式</h4>
<p>在<code>Spring Security</code>中默认使用的<code>PasswordEncoder</code>要求数据库中的密码格式为:<code>{id}password</code> 。它会根据id去判断密码的<br>
加密方式 ,所以我们之前数据库中存储的密码为<code>{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW</code></p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758029-1475502410.png" alt="image-20241112220844480" loading="lazy"></p>
<p>但是我们一般不会采用这种方式。所以就需要替换<code>PasswordEncoder</code>默认实现方式。<br>
我们一般使用<code>SpringSecurity</code>为我们提供的<code>BCryptPasswordEncoder</code>。</p>
<p>怎么替换呢?其实非常简单</p>
<p>我们只需要使用把<code>BCryptPasswordEncoder</code>对象注入<code>Spring</code>容器中,<code>SpringSecurity</code>就会使用该<code>PasswordEncoder</code>来进行密码校验。</p>
<pre><code class="language-java">@Configuration//标明这个类为配置类,spring应用程序一启动,类中的been 就会被初始化在spring容器中
@EnableWebSecurity//开启spring security 自定义配置
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
</code></pre>
<h4 id="42--controller">4.2Controller</h4>
<blockquote>
<p>添加<code>addUser</code> 方法,用于接收前台传过来的用户信息。这里省略了上一节的代码</p>
</blockquote>
<pre><code class="language-java">@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/addUser")
public int addUser(@RequestBody User user){
return userService.addUser(user);
}
}
</code></pre>
<h4 id="43-service">4.3 Service</h4>
<blockquote>
<p>由于要对前台传过来的密码进行加密处理,我们这里需要单独添加<code>addUser</code> 单独处理密码,具体方法如下</p>
</blockquote>
<p>1、<code>UserService</code></p>
<pre><code class="language-java">public interface UserService extends IService<User> {
int addUser(User user);
}
</code></pre>
<p>2、<code>UserServiceImpl</code></p>
<pre><code class="language-java">@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
UserMapper userMapper;
@Override
public int addUser(User user) {
String encode = passwordEncoder.encode(user.getPassword());
user.setPassword(encode);
user.setEnabled(true);
returnuserMapper.insert(user);
}
}
</code></pre>
<h4 id="44-测试一下">4.4 测试一下</h4>
<blockquote>
<p>到了这里,小伙伴是不是觉得已经大功告成了,我们来用postman工具测试一下</p>
</blockquote>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758074-1231025729.png" alt="报错了" loading="lazy"></p>
<p>哦豁,报<code>401 Unauthorized</code>了,这是为啥呢?</p>
<p>我们要对<code>/user/addUser</code> 进行放行</p>
<pre><code class="language-java">@Configuration//标明这个类为配置类,spring应用程序一启动,类中的been 就会被初始化在spring容器中
@EnableWebSecurity//开启spring security 自定义配置
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf-> csrf.disable());
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/addUser").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
</code></pre>
<p>添加上面代码之后,再用postman测试,用户就正常添加进去了</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758100-1593927111.png" alt="postman测试" loading="lazy"></p>
<p>我们再来看看数据库,Jon这个用户已经加进去了,而且密码123456 也是加过密的</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250521075758125-731475122.png" alt="用户成功添加到数据库中" loading="lazy"></p>
<h3 id="五小结">五、小结</h3>
<p>到此,我们已经将一个新用户添加到数据库中了,并且用户密码也做了加密处理。</p>
<p>以上操作,我们只是为了演示添加用户的基本操作,实际项目中会比这个复杂一些,不过原理都是一样的。</p>
<p>在实际的前后端分离项目中,我们还会涉及到跨域处理、统一返回结果、再插入用户之前判断用户是否存在等等。</p>
<p>小伙伴们,不用着急,后续课程中会慢慢补充~</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:程序员晓凡,转载请注明原文链接:https://www.cnblogs.com/xiezhr/p/18888163</p><br><br>
来源:https://www.cnblogs.com/xiezhr/p/18888163
頁:
[1]