SpringBoot3整合SpringSecurity6(三)基于数据库的用户认证
<p>大家好,我是晓凡。</p><h3 id="写在前面">写在前面</h3>
<p>上一篇文章中,我们了解了<code>SpringSecurity</code>怎么基于内存进行用户认证。但这还远远不够,在实际开发中。</p>
<p>用户往往都存在于数据库,所以从这篇文章开始,我们就要开始学习基于数据库的用户认证。</p>
<h3 id="一认证流程">一、认证流程</h3>
<p>其实基于数据库的用户认证和基于内存认证大同小异,我们只需要将从内存获取用户信息,换成从数据库获取用户信息即可。</p>
<p>换成代码就是替换掉<code>InMermoryUserDetailManager</code> 实现类,自己去实现<code>UserDetailsService</code>,从数据库查询用户然后返回。</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544897-2081065290.png" alt="认证流程" loading="lazy"></p>
<h3 id="二springboot3整合数据库">二、SpringBoot3整合数据库</h3>
<p>在进行认证前,我们需要保证<code>SpringBoot</code>能正常整合数据库,查询用户信息。这里我们使用的数据库是<code>MySQL8.0</code>。</p>
<h4 id="21-创建数据库表插入数据">2.1 创建数据库、表、插入数据</h4>
<p>①创建数据库</p>
<p>我们这里创建一个<code>security-demo</code>的数据库</p>
<pre><code class="language-sql">-- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;
</code></pre>
<p>② 创建用户</p>
<pre><code class="language-sql">-- 创建用户表
CREATE TABLE `user`(
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,-- 主键ID
`username` VARCHAR(50) DEFAULT NULL ,-- 用户名
`password` VARCHAR(500) DEFAULT NULL, -- 密码
`enabled` BOOLEAN NOT NULL -- 是否启用
);
</code></pre>
<p>③ 创建唯一索引</p>
<blockquote>
<p>在这里一个用户的用户名<code>username</code>字段肯定是不能重复的,所以要为<code>username</code>创建唯一索引,保证<code>username</code>的唯一</p>
</blockquote>
<pre><code class="language-sql">CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`);
</code></pre>
<p>④ 插入数据</p>
<blockquote>
<p>为了方便测试,我们默认插入几个用户。为了安全起见,这里密码采用<code>SpringSecurity</code>默认的<code>bcrypt</code>加密方式。不清楚的小伙可以先不用管,再后面的文章中会详细介绍到密码加密算法</p>
</blockquote>
<pre><code class="language-sql">-- 插入用户数据(密码是 "password" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('xiezhr', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('xiaofan', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
</code></pre>
<h4 id="22-配置lombok">2.2 配置Lombok</h4>
<p>为了偷懒,我们还引入了<code>Lombok </code>。 使用<code>Lombok </code>,可以让我们避免写<code>get</code>、<code>set</code>、<code>toString</code>等样板式代码。堪称偷懒神器,解放了双手。</p>
<img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544904-40935313.jpg" alt="Lombok" style="zoom: 50%">
<p>① 下面例举了怎么使用<code>Lombok</code> 来偷懒</p>
<ul>
<li><strong>简化 Getter 和 Setter 方法:</strong>在传统的 Java 开发中,你经常需要为每个类的属性手动编写 <code>Getter</code> 和 <code>Setter</code> 方法,但是有了 <code>Lombok</code>,你只需要在属性上加上 <code>@Getter</code> 和 <code>@Setter</code> 注解,<code>Lombok</code> 就会为你自动生成这些方法。</li>
<li><strong>自动生成构造函数:</strong>通过 <code>@NoArgsConstructor</code>、<code>@RequiredArgsConstructor</code> 或 <code>@AllArgsConstructor</code> 注解,你可以快速生成无参构造函数、带有必需参数的构造函数或者带有全部参数的构造函数。</li>
<li><strong>自动生成 equals 和 hashCode 方法:</strong> 通过 <code>@EqualsAndHashCode</code> 注解,Lombok 会根据类的字段自动生成 <code>equals()</code> 和 <code>hashCode()</code> 方法,让你的类更易于比较和使用在集合中。</li>
<li><strong>日志记录更轻松:</strong> 使用 <code>@Slf4j</code> 注解,你可以直接在类中使用 <code>log</code> 对象,而无需手动创建日志记录器。</li>
<li><strong>简化异常抛出:</strong> 通过 <code>@SneakyThrows</code> 注解,你可以在方法中抛出受检异常,而无需显式地在方法上声明或捕获它们。</li>
<li><strong>数据类简化:</strong> 使用 <code>@Data</code> 注解,Lombok 会为你自动生成所有常用方法,如 Getter、Setter、<code>toString()</code> 等,让你的数据类更加简洁。</li>
<li><strong>链式调用:</strong> 使用 <code>@Builder</code> 注解,Lombok 可以帮你创建一个更优雅的构建器模式,让你的对象初始化更加流畅。</li>
</ul>
<p>② IDEA 中安装 <code>Lombok</code> 插件</p>
<blockquote>
<p>我们需要再IDEA中安装了<code>Lombok</code>插件,才能正式愉快的使用<code>Lombok</code>。</p>
</blockquote>
<p>根据你的系统依次点击菜单:</p>
<ul>
<li>
<p>Windows 系统:<em>File -> Settings... -> Plugins</em>;</p>
</li>
<li>
<p>Mac 系统:<em>IntelliJ IDEA -> Preferences -> Plugins</em>;</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544901-267197451.png" alt="点击设置" loading="lazy"></p>
</li>
</ul>
<p>点击 <code>Marketplace</code> , 进入插件市场, 输入关键词 <code>lombok</code>, 搜索该插件:</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544924-318517314.png" alt="安装插件" loading="lazy"></p>
<p>③ 引入依赖</p>
<p>在pom.xml文件中添加如下依赖</p>
<pre><code class="language-xml"><dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
</code></pre>
<h4 id="23-引入mybatis-plus">2.3 引入Mybatis Plus</h4>
<p>为了方便后续数据库增删改查等操作,我们引入<code>MybatisPuls</code>作为持久层框架。</p>
<p>① Mybatis Plus简介</p>
<p>有些小伙伴可能还不知道<code>MybatisPuls</code>,这里简单介绍一下,知道的小伙伴直接跳过即可。</p>
<p>用白话文说<code>MybatisPuls</code>是一款操作数据库的框架。它是<strong>一个 <code>MyBatis</code> 的增强工具</strong>,就像 <code>iPhone</code>手机一般都有个 <code>plus</code> 版本一样,它在 <code>MyBatis</code> 的基础上只做增强不做改变,为简化开发、提高效率而生。</p>
<p><code>MyBatis Plus</code> 的愿景是成为 <code>MyBatis</code> 最好的搭档,就像魂斗罗中的 <code>1P</code>、<code>2P</code>,基友搭配,效率翻倍。</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544973-1304975459.png" alt="MyBatis 最好的搭档" loading="lazy"></p>
<p>② 引入依赖</p>
<blockquote>
<p>为了整合mybatis-plus,我们需要在pom.xml中引入如下依赖</p>
</blockquote>
<pre><code class="language-xml"><dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
</code></pre>
<h4 id="24-引入mysql依赖">2.4 引入MySQL依赖</h4>
<blockquote>
<p>我们这里数据库使用的是MySQL,所以还得引入MySQL相关依赖</p>
</blockquote>
<pre><code class="language-xml"><dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</code></pre>
<h4 id="25-完整依赖">2.5 完整依赖</h4>
<p>最终完整依赖如下</p>
<pre><code class="language-xml"><dependencies>
<!-- web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringSecurity 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.10</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<!--SpringBoot3整合mybatis-plus 依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--MySQL依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</code></pre>
<h4 id="26-配置数据源">2.6 配置数据源</h4>
<blockquote>
<p>application.yml文件中配置MySQL数据源,即mybatis-plus日志</p>
</blockquote>
<pre><code class="language-yml"># MySQL数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3308/security-demo
username: root
password: 123456
# MySQL日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
</code></pre>
<h4 id="27-创建实体类">2.7 创建实体类</h4>
<pre><code class="language-java">package com.xiezhr.securityindbuser.entity;
@TableName("user")
@Data
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "enabled")
private Boolean enabled;
}
</code></pre>
<ul>
<li><code>@TableName("user")</code> : <code>mybatis-plus</code>注解,用来指定数据库表名。这里实体名称与数据库表名一致,该注解可省略</li>
<li><code>@Data</code> :<code>Lombok</code>注解,用来生成<code>get</code>、<code>set</code>方法</li>
<li><code>@TableId(value = "id", type = IdType.AUTO)</code> :<code>mybatis-plus</code>注解,用来指定了字段 <code>id</code> 为表的主键,同时指定主键为自增类型</li>
<li><code>@TableField(value = "username")</code>:<code>mybatis-plus</code>注解,用来指定字段名。这里实体类属性与数据库字段一致,该注解可省略</li>
</ul>
<h4 id="28-mapper接口">2.8 Mapper接口</h4>
<pre><code class="language-java">package com.xiezhr.securityindbuser.mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
</code></pre>
<ul>
<li>继承<code>BaseMapper</code>通用类,可以默认帮我们实现基本增删改查</li>
</ul>
<h4 id="29-service">2.9 Service</h4>
<p>① 接口</p>
<pre><code class="language-java">package com.xiezhr.securityindbuser.service;
public interface UserService extends IService<User> {
}
</code></pre>
<p>② 实现</p>
<pre><code class="language-java">package com.xiezhr.securityindbuser.service.impl;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
</code></pre>
<h4 id="210-controller">2.10 Controller</h4>
<pre><code class="language-java">package com.xiezhr.securityindbuser.controller;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/list")
public List<User> getUserList(){
return userService.list();
}
}
</code></pre>
<h4 id="211-测试是否正常获取数据">2.11 测试是否正常获取数据</h4>
<p>以上小节中,我们建立了各种类。结构如下</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544877-1737802571.png" alt="代码结构" loading="lazy"></p>
<p>启动服务,浏览器中输入:http://localhost:8080/user/list看看是否能查出数据库中用户数据?</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082545011-1943160843.png" alt="查询用户数据" loading="lazy"></p>
<p>至此,我们已经成功整合数据库,并且从数据库中查询出了用户信息。</p>
<p>接下来,我们要做的就是把认证流程从内存中获取用户信息替换成我们自己实现从数据库中查询用户信息。</p>
<h3 id="三基于数据库的用户认证">三、基于数据库的用户认证</h3>
<h4 id="31-认证流程">3.1 认证流程</h4>
<p>通过之前基于内存认证分析,我们知道。只要实现<code>UserDetailsService</code> 接口的<code>loadUserByUsername</code> 方法就可以从数据库中获取用户信息。</p>
<ul>
<li>程序启动时:
<ul>
<li>创建<code>DBUserDetailsManager</code>类,实现接口 <code>UserDetailsService</code> 接口</li>
<li>在应用程序中初始化这个类的对象,使springsecurity不再从内存中获取用户信息,而是通过我们自己实现类从数据库中查询用户信息。</li>
</ul>
</li>
<li>校验用户时:
<ul>
<li>SpringSecurity自动使用<code>DBUserDetailsManager</code>的<code>loadUserByUsername</code>方法从<code>数据库中</code>获取User对象</li>
<li>在<code>UsernamePasswordAuthenticationFilter</code>过滤器中的<code>attemptAuthentication</code>方法中将用户输入的用户名密码和从数据库中获取到的用户信息进行比较,进行用户认证</li>
</ul>
</li>
</ul>
<h4 id="32--创建dbuserdetailsmanager">3.2创建<code>DBUserDetailsManager</code></h4>
<p>我们在service包下创建<code>DBUserDetailsManager</code> 来实现<code>UserDetailsService</code> 接口,替换从内存中获取用户信息。代码如下</p>
<pre><code class="language-java">package com.xiezhr.securityindbuser.service.impl;
public class DBUserDetailsManager implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
//使用username构造查询条件
QueryWrapper<User> wrapper = queryWrapper.eq("username", username);
//由于用户名不能重复,所以我们使用selectOne查询用户信息即可
User user = userMapper.selectOne(wrapper);
if (user == null) {
//用户不存在,抛出异常
throw new UsernameNotFoundException(username);
} else {
//由于现在还没有权限信息,所以我们构造一个空的权限信息
Collection< GrantedAuthority> authorities = new ArrayList<>();
return new org.springframework.security.core.userdetails.User(
user.getUsername(),//
user.getPassword(),
user.getEnabled(),
true,//用户账户是否没过期
true, //用户凭证是否没过期
true, //用户是否未被锁定
authorities //用户权限信息
);
}
}
}
</code></pre>
<h4 id="33-初始化userdetailsservice">3.3 初始化UserDetailsService</h4>
<p>说了一堆理论,那么我们怎么才能让<code>springsecurity</code>不从内存获取用户信息,而是通过上一步创建的<code>DBUserDetailsManager</code> 来查询用户信息。</p>
<p>接下来的就比较关键了,我们只需创建一个<code>WebSecurityConfig</code>,然后创建基于数据库的用户管理器<code>dbUserDetailsManager</code>即可</p>
<pre><code class="language-java">package com.xiezhr.securityindbuser.config;
@Configuration//标明这个类为配置类,spring应用程序一启动,类中的been 就会被初始化在spring容器中
@EnableWebSecurity//开启spring security 自定义配置
public class WebSecurityConfig {
@Bean
public UserDetailsService userDetailsService(){
//创建基于数据库的用户管理器
DBUserDetailsManager dbUserDetailsManager = new DBUserDetailsManager();
return dbUserDetailsManager;
}
}
</code></pre>
<p>当然我们也可以直接在<code>DBUserDetailsManager</code>类上添加<code>@Component</code>注解,也能实现同样的效果</p>
<h4 id="34-测试一下">3.4 测试一下</h4>
<p>通过上面的步骤,基于数据库的认证基本就完成了。</p>
<p>在整合数据库的时候我们插入了三个用户信息</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544990-431385601.png" alt="用户信息" loading="lazy"></p>
<p>下面我们来测试下成果,浏览器中输入:http://localhost:8080/user/list</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082545008-1649277178.png" alt="接口访问测试" loading="lazy"></p>
<p>随便输入上面三个用户中一个,<code>admin/password</code><code>xiezhr/password</code> <code>xiaofan/password</code> 即可正常访问接口</p>
<p><img src="https://img2024.cnblogs.com/blog/2381533/202505/2381533-20250506082544988-482688621.png" alt="成功访问接口" loading="lazy"></p>
<p>到此,我们成功完成了基于数据库的用户认证功能,是不是很简单呢~</p>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:程序员晓凡,转载请注明原文链接:https://www.cnblogs.com/xiezhr/p/18860890</p><br><br>
来源:https://www.cnblogs.com/xiezhr/p/18860890
頁:
[1]