文章目录
- 准备工作
- 新建项目
- 引入MyBatis-Plus依赖
- 创建表结构
- 生成基础代码
- 逻辑实现
- `application.yml`配置
- SecurityConfig 配置
- 自定义 UserDetailsService
- 创建测试
- 启动测试
在前面的文章中我们介绍了 《Spring Boot 3 集成 Spring Security(1)认证》和 《Spring Boot 3 集成 Spring Security(2)授权》,这篇博客将介绍如何在 Spring Boot 3 项目中,整合 Spring Security 和 MyBatis-Plus ,轻松实现基于数据库的用户访问控制、权限管理。
准备工作
新建项目
springboot3-security-mysql-example 引入依赖
引入MyBatis-Plus依赖
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
注意事项
版本 3.5.9+ 开始修改为可选依赖,具体查看下文 maven bom 部分。
<mybatisplus.version>3.5.9</mybatisplus.version>
<!-- MyBatis-Plus https://baomidou.com-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-bom</artifactId>
<version>${mybatisplus.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建表结构
这里我们定义三张表,来实现用户角色权限的操作
-- 用户表
CREATE TABLE `sys_user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` VARCHAR ( 64 ) DEFAULT NULL COMMENT '用户名',
`password` VARCHAR ( 64 ) DEFAULT NULL COMMENT '密码',
`sex` CHAR ( 1 ) DEFAULT '0' COMMENT '性别 0 男 1 女 2 未知',
`nick_name` VARCHAR ( 64 ) DEFAULT NULL COMMENT '昵称',
`status` CHAR ( 1 ) DEFAULT '1' COMMENT '账号状态 0 禁用 1 启用',
`valid` INT DEFAULT '1' COMMENT '有效状态 0 无效 1 有效',
PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT = '用户';
INSERT INTO `sys_user` (`id`, `username`, `password`, `sex`, `nick_name`, `status`, `valid`) VALUES (1, 'admin', '$2a$10$xZdonloiiL6YfoLZv6mrJuvxtD238uHPIKkVDpQKBuZxzMDpTf8uK', '0', '管理员张三', '1', 1);
INSERT INTO `sys_user` (`id`, `username`, `password`, `sex`, `nick_name`, `status`, `valid`) VALUES (2, 'user', '$2a$10$evM9SfvuN/E.ykWWOf6b3eTltPvuc6XjwW/qIhagSjlsTfi9l26Ba', '0', '用户李四', '1', 1);
-- 角色表
CREATE TABLE `sys_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` VARCHAR ( 64 ) DEFAULT NULL COMMENT '角色名',
`code` VARCHAR ( 64 ) DEFAULT NULL COMMENT '密码',
`status` CHAR ( 1 ) DEFAULT '1' COMMENT '状态 0 禁用 1 启用',
`valid` INT DEFAULT '1' COMMENT '有效状态 0 无效 1 有效',
PRIMARY KEY ( `id` )
) ENGINE = INNODB COMMENT = '角色';
INSERT INTO `sys_role` (`id`, `name`, `code`, `status`, `valid`) VALUES (1, '管理员', 'ROOT', '1', 1);
INSERT INTO `sys_role` (`id`, `name`, `code`, `status`, `valid`) VALUES (2, '普通用户', 'USER', '1', 1);
-- 用户角色关系表
CREATE TABLE `sys_user_role` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
`user_id` bigint DEFAULT NULL COMMENT '用户ID',
`role_id` bigint DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB COMMENT = '用户角色关系表';
INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`) VALUES (1, 1, 1);
INSERT INTO `sys_user_role` (`id`, `user_id`, `role_id`) VALUES (2, 2, 2);
默认设置账户密码
123456
,在数据库中使用加密后的密码,关于密码加密,可以使用下面的测试方法。
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String result = encoder.encode("123456");
// 输出加密后的密码
System.out.println(result);
// 对比加密后的密码和明文密码
System.out.println(encoder.matches("123456", result));
}
生成基础代码
这里我用了代码生成器插件,以提高生产效率,想具体了解,可以去官网上搭建使用。这里就不多说啦
本地代码勾选,使用 mybatis-plus 3
准备工作到这里基本上就可以了,接下来开始实现从数据库中读取用户角色权限
逻辑实现
application.yml
配置
spring:
thymeleaf:
# 设置Thymeleaf模板文件的前缀位置(默认是`src/main/resources/templates`)
prefix: classpath:/templates/
# 设置模板文件的后缀(默认是`.html`)
suffix: .html
# 设置模板模式(默认是HTML5,Thymeleaf 3中为`HTML`)
mode: HTML
# 开启模板缓存(开发时建议关闭,生产时开启)
cache: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/security_data?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
#mybatis
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: cn.harry.*.domain
global-config:
#数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
# 逻辑删除全局属性名(驼峰和下划线都支持)
logic-delete-field: valid
logic-delete-value: 0 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 1 # 逻辑未删除值(默认为 0)
banner: false
#原生配置
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
call-setters-on-nulls: true
jdbc-type-for-null: 'null'
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
SecurityConfig 配置
要使用 Spring Security 进行用户认证,我们需要配置 SecurityConfig,并实现自定义的 UserDetailsService 来与数据库中的用户信息进行集成。
/**
* @author harry
* @公众号 Harry技术
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true) // 开启方法级别的权限控制
@RequiredArgsConstructor
public class SecurityConfig {
// 通过构造函数注入自定义UserDetailsService
private final UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 公开访问
.requestMatchers("/").permitAll()
// 其他接口需认证
.anyRequest().authenticated()
)
.userDetailsService(userDetailsService)
// .exceptionHandling(exception -> exception
// .authenticationEntryPoint(restAuthenticationEntryPoint)
// .accessDeniedHandler(restfulAccessDeniedHandler)
// )
// 开启基于表单的登录
.formLogin(Customizer.withDefaults())
// // 开启注销功能
// .logout(Customizer.withDefaults())
// // 开启 HTTP Basic 认证
// .httpBasic(Customizer.withDefaults())
// // 开启 CSRF 防护
// .csrf(Customizer.withDefaults())
// // 开启跨域资源共享
// .cors(Customizer.withDefaults())
;
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
// 使用 BCrypt 进行密码加密
return new BCryptPasswordEncoder();
}
自定义 UserDetailsService
想从数据库加载用户信息,就需要创建一个自定义的 UserDetailsService 实现类,它的主要作用:
用户认证:
UserDetailsService 负责从数据源(如数据库、LDAP等)中加载用户特定的安全信息,包括用户名、密码和权限(角色)。
Spring Security 使用 UserDetailsService 来验证用户提供的凭据是否正确。
用户授权:
加载用户的权限信息,以便在认证成功后进行授权检查。
权限信息通常包括用户的角色(如 ROLE_ADMIN, ROLE_USER 等),这些角色用于控制用户可以访问的资源和操作。
/**
* 系统用户认证 service
*
* @author harry
* @公众号 Harry技术
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final SysUserMapper sysUserMapper;
private final SysUserRoleMapper sysUserRoleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
// 获取登录用户信息
SysUser user = sysUserMapper.selectByUsername(username);
// 用户不存在、用户停用 等校验 TODO
Long useId = user.getId();
// 获取角色
Set<String> roles = sysUserRoleMapper.listRoleKeyByUserId(useId);
return new SysUserDetails(user, roles, username);
} catch (Exception e) {
log.error("loadUserByUsername error", e);
}
return null;
}
}
我们根据数据库中的用户信息加载用户,并将角色转换为 Spring Security 能识别的格式。我们写一个SysUserDetails
类来实现自定义Spring Security 用户对象。
/**
* 自定义 Spring Security 用户对象
*
* @author harry
* @公众号 Harry技术
*/
@Data
@NoArgsConstructor
public class SysUserDetails implements UserDetails {
private String username;
private SysUser sysUser;
private Collection<SimpleGrantedAuthority> authorities;
public SysUserDetails(SysUser user, Set<String> roles, String username) {
this.sysUser = user;
Set<SimpleGrantedAuthority> authorities;
if (CollectionUtil.isNotEmpty(roles)) {
// 标识角色 前面加上 ROLE_
authorities = roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toSet());
} else {
authorities = Collections.emptySet();
}
this.authorities = authorities;
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 返回当前用户的权限
return authorities;
}
@Override
public String getPassword() {
return sysUser.getPassword();
}
@Override
public String getUsername() {
return this.username;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return 是否可用
*/
@Override
public boolean isEnabled() {
return StatusEnums.ENABLE.getKey().equals(sysUser.getStatus());
}
}
创建测试
- 页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.content {
width: 800px;
height: 800px;
text-align: center;
line-height: 100px;
font-size: 20px;
flex: 1;
flex-direction: column;
display: flex;
justify-content: center;
align-items: center;
}
</style>
</head>
<body>
<div class="content">
<!--去登陆 -->
<a href="/login">去登陆</a>
<!-- admin/info 接口 -->
<a href="/admin/info">访问 admin/info 接口</a>
<!-- 去首页 -->
<a href="/user/info">访问 user/info 接口</a>
<!--退出-->
<a href="/logout">退出</a>
</div>
</body>
</html>
-
接口
改写接口
admin/info
,并配置@PreAuthorize("hasRole('ROOT')")
只有 ADMIN 角色才能访问
/**
* @author harry
* @公众号 Harry技术
* Spring Boot 3 集成 Spring Security(2) 授权: https://mp.weixin.qq.com/s/HzzcYIQLnch_7r7wdUarew
*/
@Slf4j
@RestController
public class AdminController {
@GetMapping("/admin/info")
@PreAuthorize("hasRole('ROOT')") // 只有 ADMIN 角色才能访问
public SysUserDetails adminInfo() {
// 获取当前登录的用户信息
SysUserDetails user = (SysUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("当前登录的用户信息:{}", user.toString());
return user;
}
}
改写接口user/info
,并配置 @PreAuthorize("hasRole('USER')")
只有 USER 角色才能访问
@Slf4j
@RestController
public class UserController {
@GetMapping("/user/info")
@PreAuthorize("hasRole('USER')") // 只有 user 角色才能访问
public SysUserDetails userInfo() {
// 获取当前登录的用户信息
SysUserDetails user = (SysUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("当前登录的用户信息:{}", user.toString());
return user;
}
}
启动测试
- 1.登录admin账户
访问 admin/info 接口
访问 user/info 接口
- 2.登录user用户
访问 admin/info 接口
访问 user/info 接口
通过上面的测试用例,通过定义用户和角色实体、实现自定义的 UserDetailsService,实现了数据库驱动的用户认证和基于角色的授权机制。这种结合方式不仅在安全性上提供了极大的灵活性,也让数据管理变得更加简洁高效。
关注我,在后续的文章中,我们进一步探讨如果使用JWT、OAuth2 等机制、使用Redis作为缓存来强化认证与授权的实现。
示例源码:关注公众号“Harry技术”,回复 security 获取源码地址。