文章目录
- [spirngSecurity 鉴权教程02](#spirngSecurity 鉴权教程02)
-
- [使用 MyBatis-Plus](#使用 MyBatis-Plus)
-
- [1. 引入依赖](#1. 引入依赖)
- [2. 安装 IDEA 插件](#2. 安装 IDEA 插件)
- [3. 配置数据源](#3. 配置数据源)
- [4. 准备数据表](#4. 准备数据表)
-
- [利用 MyBatisX 生成 Mapper、Service、Controller、POJO](#利用 MyBatisX 生成 Mapper、Service、Controller、POJO)
- 重写userDetailsService
-
-
- [1. 为什么需要重写 UserDetailsService](#1. 为什么需要重写 UserDetailsService)
- [2. 编写实现类](#2. 编写实现类)
- [3. 配置 PasswordEncoder](#3. 配置 PasswordEncoder)
- [4. 生成一条可用的测试密码](#4. 生成一条可用的测试密码)
- [6.完善SecurityConfig 的配置](#6.完善SecurityConfig 的配置)
- [5. 验证认证流程](#5. 验证认证流程)
-
spirngSecurity 鉴权教程02
使用 MyBatis-Plus
在开始写认证逻辑之前,先把持久层准备好。本教程使用 MyBatis-Plus 作为数据访问框架,它在 MyBatis 的基础上提供了通用 CRUD、条件构造器、分页插件等能力,能显著减少样板代码。
1. 引入依赖
在 pom.xml 中添加 MyBatis-Plus 的 starter 与 MySQL 驱动:
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
2. 安装 IDEA 插件
- 打开 IDEA 的
Settings → Plugins,在 Marketplace 中搜索MyBatisX; - 点击
Install安装并重启 IDE; - 安装后,Mapper 接口与 XML 之间会出现双向跳转箭头,编写 SQL 时还能获得表名、字段名的自动补全。
3. 配置数据源
在 application.yml 中配置数据库连接与 MyBatis-Plus 的基础参数:
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
其中 map-underscore-to-camel-case 让数据库的 user_name 自动映射到实体的 userName,log-impl 则把执行的 SQL 打印到控制台,方便调试。
4. 准备数据表
认证功能至少需要用户表与权限表,这里给出一个最小可用的结构:
sql
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
nickname VARCHAR(50),
status TINYINT DEFAULT 1 COMMENT '1 启用 0 禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
role_code VARCHAR(50) NOT NULL COMMENT '如 ROLE_ADMIN'
);
利用 MyBatisX 生成 Mapper、Service、Controller、POJO
建好表之后,如果还手写实体、Mapper、Service、Controller,既费时又容易写错字段。MyBatisX 插件提供了「代码生成」能力,可以基于数据库表结构一键生成整套骨架代码。
第一步:连接数据库
在 IDEA 右侧边栏找到 Database 面板,点击 + → Data Source → MySQL,填写主机、端口、数据库名、用户名和密码,然后点击 Test Connection。测试通过后点击 OK,即可在面板中看到 security_demo 库下的所有表。
第二步:打开生成入口
在 Database 面板中选中 sys_user 表,右键选择 MybatisX-Generator。如果菜单里没有这一项,说明插件未启用,回到 Settings → Plugins 确认 MyBatisX 已安装并重启 IDE。
第三步:填写生成配置
弹出的向导中依次填写以下信息:
module path:选择当前项目模块,例如security-demo;base package:基础包名,例如com.example.security;relative package:实体类所在子包,例如entity;encoding:选择UTF-8;annotation:选择MyBatis-Plus 3,这样生成的实体会自动带上@TableName、@TableId等注解;options:勾选Lombok、Comment、Actual Column,让实体字段带注释并自动生成 getter/setter。
第四步:选择要生成的模板
向导最后一步会列出可生成的模板,勾选需要的即可:
entity:生成User实体类;mapper:生成UserMapper接口,并自动继承BaseMapper<User>;service:生成UserService接口与UserServiceImpl实现类,继承IService/ServiceImpl;controller:生成UserController,内置基础 CRUD 接口;xml:生成UserMapper.xml,用于编写自定义 SQL。
点击 Finish 后,IDEA 会在对应包下生成全部文件。以 UserMapper 为例,生成结果大致如下:
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
UserServiceImpl 则可以直接调用 save、getById、list 等通用方法,无需再写一行 SQL:
java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
第五步:补充自定义查询
自动生成的代码只覆盖通用 CRUD,认证场景还需要「根据用户名查用户」和「根据用户 ID 查权限」。这两个方法手动补到 Mapper 中即可:
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM sys_user WHERE username = #{username}")
User selectByUsername(String username);
@Select("SELECT role_code FROM sys_role WHERE user_id = #{userId}")
List<String> selectAuthoritiesByUserId(Long userId);
}
第六步:插入测试数据
为了后续调试认证流程,先往表里插入一条测试账号:
sql
INSERT INTO sys_user (username, password, nickname, status)
VALUES ('admin', '$2a$10$abcdefghijklmnopqrstuv', '管理员', 1);
INSERT INTO sys_role (user_id, role_code) VALUES (1, 'ROLE_ADMIN');
注意这里的 password 是 BCrypt 加密后的密文,不能直接存明文,否则 Spring Security 校验时会失败。明文与密文的转换方式会在下一节重写 UserDetailsService 时详细说明。
至此,持久层的骨架代码已经全部就绪:实体、Mapper、Service、Controller 都由 MyBatisX 生成,只需补充少量自定义查询。下一节将基于这套 Mapper 重写 UserDetailsService,把数据库中的用户交给 Spring Security 完成认证。
重写userDetailsService
- 在
service/impl/UserServiceImpl中实现UserDetailsService接口,重写loadUserByUsername方法; - 通过
UserMapper.selectByUsername查询用户,查不到时抛出UsernameNotFoundException; - 通过
UserMapper.selectAuthoritiesByUserId查询该用户的权限,转换为GrantedAuthority集合; - 将用户信息封装为
UserDetails对象返回,交由 Spring Security 完成后续的密码校验与授权。
1. 为什么需要重写 UserDetailsService
Spring Security 的认证流程本身并不关心用户数据存在哪里,它只认一个约定:只要你能提供一个 UserDetailsService 实现,并在 loadUserByUsername(String username) 中根据用户名返回一个 UserDetails,框架就会接管后续的密码比对、认证状态维护和权限装载。
默认情况下,Spring Boot 会自动配置一个基于内存的 InMemoryUserDetailsManager,用户名固定为 user,密码在启动日志里随机打印。这种配置只适合快速验证,一旦接入真实数据库就必须替换掉。重写 UserDetailsService 正是把「数据库中的用户」与「Spring Security 的认证体系」连接起来的关键一步。
2. 编写实现类
在 UserServiceImpl 上实现 UserDetailsService 接口。由于该类已经继承了 ServiceImpl<UserMapper, User>,可以直接复用注入好的 baseMapper,无需再额外注入一次 UserMapper:
java
package com.yjze.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yjze.pojo.User;
import com.yjze.service.UserService;
import com.yjze.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
/**
* @author 马涵博
* @description 针对表【user】的数据库操作Service实现
* @createDate 2026-10-26 19:54:55
*/
@Service
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(lambdaQueryWrapper);
if (user == null) {
throw new UsernameNotFoundException("user"+username+" not found");
}
ArrayList<GrantedAuthority> list = new ArrayList<>();
return new org.springframework.security.core.userdetails.User(username, user.getPassword(),list);
}
}
这里有几个细节值得注意:
- 密码必须是密文 :
password(user.getPassword())传入的是数据库中存储的 BCrypt 密文,Spring Security 会用配置好的PasswordEncoder与之比对,因此千万不要在这里做解密或明文替换; - 权限前缀 :
role_code存的是ROLE_ADMIN这类带前缀的值,SimpleGrantedAuthority会原样保存,后续hasRole("ADMIN")才能正确匹配; - 账号状态 :通过
disabled(...)把status = 0的账号标记为禁用,登录时框架会直接抛出DisabledException,无需自己写判断。
3. 配置 PasswordEncoder
重写 UserDetailsService 之后,还需要告诉 Spring Security 用哪种方式校验密码,否则启动时会报错:There is no PasswordEncoder mapped for the id "null"。解决办法很简单,在配置类 SecurityConfig 中声明一个 BCrypt 编码器即可:
java
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
BCryptPasswordEncoder 每次加密都会随机加盐,因此同一明文多次加密得到的密文并不相同;校验时由 matches(rawPassword, encodedPassword) 内部完成盐值提取与比对,无需自己处理。
4. 生成一条可用的测试密码
前面插入的测试数据里,密码只是一段占位密文,直接登录必然校验失败。可以写一个临时测试方法生成真实密文,再把结果更新到数据库中:
java
@Test
void generatePassword() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("123456"));
}
将输出的密文替换掉 sys_user 表中 admin 的 password 字段,之后用 admin / 123456 即可通过认证。
6.完善SecurityConfig 的配置
java
package com.yjze.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
@Configuration //配置Spring容器 类似xml配置文件
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 配置Spring Security的一些行为 配置我们自己的登录页面 不用默认的登录页面
//SecurityFilterChain 是Spring Security的过滤链链 有实现类 叫DefaultSecurityFilterChain
//过滤链链中包含了多个过滤器
//每个过滤器都有自己的责任
//过滤器之间的责任是通过链式调用实现的
//过滤器链链的顺序是固定的
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//如果我们配置类自己的 securityFilterChain 他默认的 过滤连 就不会生效
//对登录页面进行放行
http
.authorizeHttpRequests((authorizeHttpRequests) ->{
authorizeHttpRequests.requestMatchers("/login.html")
.permitAll()//放行
.anyRequest().authenticated(); //其他请求都需要认证
});
http.formLogin(formLogin -> {
// 指定自定义登录页,替代 Spring Security 默认登录页
formLogin.loginPage("/login.html")
// 表单提交的登录请求地址,由 UsernamePasswordAuthenticationFilter 拦截处理
.loginProcessingUrl("/user/login")
// 登录成功后转发到的页面
.successForwardUrl("/index")
// 登录失败后跳转的页面
.failureUrl("/err");
});
http.csrf(csrf -> csrf.disable());
DefaultSecurityFilterChain build = http.build();
return build;
}
}
- 配置后就无需进行 手动在 service层加密
5. 验证认证流程
启动项目后,Spring Security 默认会对所有请求开启认证。访问任意接口都会被重定向到登录页,输入 admin / 123456 进行验证:
- 登录成功:说明
loadUserByUsername正确返回了用户与权限,密码比对通过; - 提示「用户名或密码错误」:优先检查数据库中的密码是否为 BCrypt 密文,以及
PasswordEncoder是否已注册为 Bean; - 提示「用户不存在」:说明
selectByUsername没有查到数据,检查表名、字段名与测试数据是否一致。
至此,认证链路已经打通:请求进入 → UserDetailsService 加载用户 → PasswordEncoder 校验密码 → 权限写入 SecurityContext。下一节将在此基础上配置登录接口与权限拦截规则,让不同角色访问不同的接口。