一. 前言
随着项目进一步开发,有些问题函待解决:
- 用户多了就需要多个管理员和管理平台进行管理,如何区分?
- 虽然可以使用if-else遇到管理员身份可以直接重定向,能不能优化?
所以打算通过
SpringSecurity+MP+H2数据库实现一个小demo解决该问题
流程
主要流程

总体上demo用户认证的流程如下
用户提交表单
UsernamePasswordAuthenticationFilter
封装未认证令牌
ProviderManager
查询用户+密码比对
生成已认证Authentication
存入SecurityContextHolder
业务接口访问
二、主要框架
1.SecurityFilterChain
core
决定了一个请求需要经过哪些过滤器
-
Security5.7以后统一使用
@Bean定义SecurityFilterChain -
所有的URL权限规则、登录页面配置、异常处理都是在这条链上进行处理
2.Authentication对象
是一个数据载体,对于当前项目,它包含了用户名与密码,经过一系列拦截器后包含了其他的信息要素
- 它通过
SecurityContextHolder获取当前登录用户
3.AuthenticationManager
负责接受Authentication请求,然后委托然后委托 给具体的 AuthenticationProvider 去处理
DaoAuthenticationProvider:专门负责核对数据库账号密码。OAuth2LoginAuthenticationProvider:专门负责核对第三方登录。- 如果需要自定义特殊的登录逻辑,就需要自定义一个Provider
4.UserDetailsService&UserDetails
UserDetailsService负责根据用户名去数据库/Redis进行查找
UserDetails用户的信息,检查是否有有权限操作、是否过期
- 需要实现
loadUserByUsername方法,把数据库里的实体转换成UserDetails对象
5. PasswordEncoder
负责面的加密和匹配
- Spring Security 强制要求密码不能明文存储。
- 你必须定义一个
PasswordEncoder的 Bean(推荐BCryptPasswordEncoder)。 - 登录时,
AuthenticationProvider会拿着用户输入的明文密码,用它算一遍,再和数据库里的密文比对。如果没配这个,登录永远报 "Bad Credentials"。
最终形成该流程
公开资源
受保护资源
否
是
用户请求
SecurityFilterChain
直接放行
是否登录?
跳转登录页
访问目标
三、搭建demo
1. 项目依赖 (pom.xml)
选择了轻量的 MP,同时为了方便集成使用,加入了H2数据库和AI简易生成的页面进行模拟
注意 : Spring Boot 3 需要搭配
mybatis-plus-spring-boot3-starter。
xml
<!-- 核心依赖片段 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!--Web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--Thymeleaf Spring Security-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.27</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2. 核心代码实现
2.1 实体类与 Mapper
使用 MP,我们只需继承
BaseMapper即可获得全套 CRUD 能力,无需编写任何 XML 文件。
实体类
java
//User.java
@Data
@TableName("users") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 自增主键
private Long id;
private String username;
private String password;
private String role;
}
//Post.java
@Data
@TableName("posts")
public class Post {
//自增主键
@TableId(type = IdType.AUTO)
private Long id;
private String title;
private String content;
private String author;
}
java
//UserMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
//PostMapper
@Mapper
public interface PostMapper extends BaseMapper<Post> {
}
2.2 自定义用户认证逻辑
Spring Security 需要通过
UserDetailsService接口来加载用户信息。我们利用 MP 的LambdaQueryWrapper进行类型安全的查询。
实现类
java
@Service
public class CustomerUserDetailsServiceImpl implements UserDetailsService {
private final UserMapper userMapper;
public CustomerUserDetailsServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 根据用户名查询用户信息
* @param username 用户名
* @return UserDetails 用户信息
* @throws UsernameNotFoundException 用户名不存在
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,username);
//1.查询用户信息
User user= userMapper.selectOne(wrapper);
//2.如果不存在返回错误
if(user==null)throw new UsernameNotFoundException("用户不存在"+username);
//3.通过Userdetails的构造方法返回User实例
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
List.of(new SimpleGrantedAuthority(user.getRole()))
);
}
}
使用
User::getUsername方法引用,避免了硬编码字符串字段名,重构更安全。
3.3 安全配置链 (SecurityFilterChain)
这是最关键的部分。我们需要明确告诉 Security:哪些路径公开,哪些需要登录,以及登录页面的地址。
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//BCrypt编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 创建过滤器链
* @param http HTTP安全配置对象
* @param userDetailsService 自定义的用户详情服务
* @return 配置好的安全过滤器链
* @throws Exception 配置过程中可能抛出的异常
*/
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http, // HttpSecurity 配置入口
CustomerUserDetailsServiceImpl userDetailsService // 注入自定义用户认证逻辑
)throws Exception {
// 配置用户详情服务
http
//注册MP服务
.userDetailsService(userDetailsService)
//配置权限
.authorizeHttpRequests(auth->auth
//requestMatchers()方法用于匹配请求,在这些URL中,任何用户都可以访问
.requestMatchers("/","/posts/","/login","/register","/h2-console/**","/css/**")
// permitAll()方法表示任何用户都可以访问
.permitAll()
// 匹配admin下的所有URL,只有ADMIN角色可以访问
.requestMatchers("/admin/**").hasRole("ADMIN")
// 匹配user下的所有URL,USER和ADMIN角色都可以访问
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
// 匹配任何URL, authenticated()方法限制只有登录的用户才能访问
.anyRequest().authenticated()
)
//配置登录
.formLogin(form->form
// 登录页面
.loginPage("/login")
// 登录处理
.loginProcessingUrl("/dologin")
// 登录成功后跳转到首页(用户可自行访问个人中心或管理后台)
.defaultSuccessUrl("/")
// 登录失败后跳转
.failureUrl("/login?error")
// 允许所有用户访问
.permitAll()
)
//配置登出,适用于所有用户
.logout(logout->logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutSuccessUrl("/login?logout")
.permitAll()
)
//配置CSRF,ignoringRequestMatchers()方法忽略h2-console下的所有请求
.csrf(csrf->csrf.ignoringRequestMatchers("/h2-console/**"));
//返回HTTP对象
return http.build();
}
}
3.4H2数据库配置
使用CommandLineRunner中的init初始化数据库,添加相关信息
java
@Configuration
public class DataInitializer {
/**
* 初始化用户
* @param userMapper 用户映射
* @param encoder 密码编码器
* @return 命令行运行器
*/
@Bean
CommandLineRunner init(UserMapper userMapper, PasswordEncoder encoder) {
return args -> {
if (userMapper.selectCount(null) == 0) {
// 创建管理员用户
User admin = new User();
admin.setUsername("admin");
admin.setPassword(encoder.encode("123456"));
admin.setRole("ROLE_ADMIN");
userMapper.insert(admin);
// 创建普通用户
User user = new User();
user.setUsername("user");
user.setPassword(encoder.encode("123"));
user.setRole("ROLE_USER");
userMapper.insert(user);
System.out.println(">>> [SpringSecurityTest2] 初始用户已创建");
System.out.println(">>> 管理员 - 用户名: admin, 密码: 123456");
System.out.println(">>> 普通用户 - 用户名: user, 密码: 123");
}
};
}
}
同时因为是一次性数据库,需要写入创建数据库脚本,方便每次启动时自动执行,同时也要在yaml文件中打开自动执行功能
mysql
-- data.sql
-- 密码 '123' 的 BCrypt 加密结果
INSERT INTO users (username, password, role) VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'ROLE_ADMIN');
INSERT INTO users (username, password, role) VALUES ('user', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi', 'ROLE_USER');
-- 插入测试文章
INSERT INTO posts (title, content, author) VALUES ('First Post', 'Hello World', 'admin');
-- schema.sql
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
content TEXT,
author VARCHAR(255)
);
yaml
spring:
sql:
init:
mode: always #始终执行初始数据库的脚本
datasource:
url: jdbc:h2:mem:mdblog_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
driver-class-name: org.h2.Driver
username: sa
password: 123456
h2:
console:
enabled: true
path: /h2-console
四、 避坑
1.ERR_TOO_MANY_REDIRECTS

Q :配置完成后,访问后台页面,浏览器疯狂跳转,最终报错 ERR_TOO_MANY_REDIRECTS。
A:这是一个经典的逻辑死循环:
-
用户访问
/admin/dashboard-> 未登录 -> Security 拦截 -> 重定向到/login。 -
用户访问
/login-> 如果/login没有被显式放行 -> Security 再次拦截(认为你没登录) -> 再次重定向到/login。 -
无限循环... 🔄
解决方案 :
必须在 authorizeHttpRequests 中,显式且准确地放行登录页及相关资源。
java
.requestMatchers("/", "/login", "/dologin", "/h2-console/**", "/css/**").permitAll()
注意细节:
/dologin(表单提交地址) 也必须放行,否则 POST 请求会被拦截。/h2-console/**建议使用双星号通配符,覆盖所有子路径。- 确保
formLogin().loginPage("/login")中的 URL 与放行列表完全一致。
5. 运行效果展示
启动日志分析 :
观察控制台日志,可以看到 H2 数据库启动成功,MyBatis-Plus 打印出了初始化的 SQL,且 Security 过滤器链构建完成。
H2数据库成功启动
过滤器链构建完成
功能演示:
- 访问
http://localhost:8080/admin/dashboard,自动跳转至登录页。- 输入测试账号
admin/user/123456,登录成功进入后台。- 点击退出,会话清除,再次访问需重新登录。
6. 总结
通过本文,成功整合了 Spring Security 与 MyBatis-Plus。虽然 Security 的配置略显繁琐,但只要理清"过滤器链"和"权限匹配顺序"这两个核心概念,就能轻松驾驭。
MP 的加入更是大大简化了数据层代码,让我们可以更专注于业务逻辑。
同时上传了demo源代码,有兴趣的可以下载运行尝试,可能在登录后会有404问题,建议登录后修改URL为:http://localhost:8080/即可解决问题
下一步计划 :
后续会融入非遗项目中做进一步的整合与融合




