本章节将介绍如何通过 Spring Security + 数据库(MySQL)实现权限登陆认证,授权将在后续章节介绍。
下面将使用 MySQL 数据库,分别创建用户(users)、角色(role)、菜单(menu)、角色用户(role_user)、角色菜单(role_menu)数据库表,如下图:

首先创建数据库 spring_security,然后执行如下 SQL 脚本:
-- 用户信息表
create table users(
id bigint primary key auto_increment,
username varchar(20) unique not null,
password varchar(100)
);
-- 密码:123456
insert into users values(1,'zhangsan','$2a$10$5wJSJGpCj1dFRKTWjdTyJ.NI2CTwFsBIyf.o8A07sH.MtqMFAbmWi');
-- 密码:aaaaaa
insert into users values(2,'lisi','$2a$10$BaNPfmY3euVmVlI/E2V7CedEYHyPuBINMVALTNKKQI6KxILhUOB3W');
-- 角色表
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`code` varchar(20) DEFAULT NULL COMMENT '角色编码',
`name` varchar(20) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`)
);
insert into role values(1,'admin','管理员');
insert into role values(2,'normal','普通用户');
-- 角色用户表
create table role_user(
uid bigint,
rid bigint
);
insert into role_user values(1,1);
insert into role_user values(2,2);
-- 菜单表
create table menu(
id bigint primary key auto_increment,
name varchar(20),
url varchar(100),
parentid bigint,
permission varchar(20)
);
insert into menu values(1,'系统管理','',0,'menu:sys');
insert into menu values(2,'用户管理','',0,'menu:user');
-- 角色菜单表
create table role_menu(
mid bigint,
rid bigint
);
insert into role_menu values(1,1);
insert into role_menu values(2,1);
insert into role_menu values(2,2);执行完上述 SQL 脚本后,数据库表如下:

其中,users 数据表的数据如下,存在两个用户:

为了演示使用 Spring Security 结合数据库实现登录功能,创建 spring_security_demo3 演示项目,项目结构如下图:

后续将逐一介绍该演示项目各个文件的作用和源码。
完整的 maven 依赖请参考“Spring Security 入门示例”,下面是新增的 mybatis 和 mysql 依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.14</version>
</dependency>在 application.properties 配置文件中新增数据源配置信息,如下:
spring.application.name=spring_security_demo3
# 数据库配置信息
# 数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库JDBC地址
spring.datasource.url=jdbc:mysql://localhost:3306/spring_security?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=UTF-8
# 用户名
spring.datasource.username=root
# 密码
spring.datasource.password=aaaaaa如果采用 YAML 格式,配置类似。
编写一个 User 实体,用于建立与 users 表的映射,如下:
package com.hxstrive.spring_security.model;
import java.io.Serializable;
public class User implements Serializable {
/** 用户ID */
private Integer id;
/** 用户名 */
private String username;
/** 密码 */
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}定义一个 MyBatis Mapper,实现根据用户名查询用户信息,如下:
package com.hxstrive.spring_security.mapper;
import com.hxstrive.spring_security.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* 用户Mapper
* @author hxstrive.com
*/
@Mapper
public interface UserMapper {
/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 返回用户信息
*/
@Select({"select * from users where username = #{username}"})
User selectUserByUserName(@Param("username") String username);
}定义一个服务,该服务用于根据用户名查询用户信息:
package com.hxstrive.spring_security.service;
import com.hxstrive.spring_security.model.User;
/**
* 用户服务接口
* @author hxstrive.com
*/
public interface UserService {
/**
* 根据用户名查询用户信息
* @param username 用户名
* @return 用户信息
*/
User getUserByName(String username);
}通过调用 UserMapper 来实现 UserService 服务接口的 getUserByName() 方法查询用户,如下:
package com.hxstrive.spring_security.service.impl;
import com.hxstrive.spring_security.mapper.UserMapper;
import com.hxstrive.spring_security.model.User;
import com.hxstrive.spring_security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 用户服务实现
* @author hxstrive.com
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserByName(String username) {
return userMapper.selectUserByUserName(username);
}
}编写自定义的 UserDetailsService 实现类,该类将使用 UserService 服务从数据库中查询用户信息,如下:
package com.hxstrive.spring_security.config;
import com.hxstrive.spring_security.service.UserService;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 自定义实现 UserDetailsService 接口
* @author hxstrive.com
*/
public class CustomUserDetailsService implements UserDetailsService {
// 用户服务
private UserService userService;
public CustomUserDetailsService(UserService userService) {
this.userService = userService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("loadUserByUsername(String username) :: " + username);
// 从数据库中加载用户信息
com.hxstrive.spring_security.model.User user = userService.getUserByName(username);
if(null == user){
// 用户名不存在。抛出异常
throw new UsernameNotFoundException("用户名 [" + username + "] 不存在");
}
// 创建 UserDetails 对象
return new User(username, user.getPassword(), AuthorityUtils.createAuthorityList());
}
}添加配置类,将自定义的 CustomUserDetailsService 和 BCryptPasswordEncoder 注入到 Spring Security 中,如下:
package com.hxstrive.spring_security.config;
import com.hxstrive.spring_security.service.UserService;
import jakarta.annotation.Resource;
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.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* Spring Security 配置类
* @author hxstrive.com
*/
@Configuration
public class SecurityConfig {
@Resource
private UserService userService;
// 自定义的 UserDetailsService
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService(this.userService);
}
// 配置密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
// formLogin 用于配置表单登录功能
// permitAll() 表示允许所有用户访问表单登录相关的页面,例如登录页面、登录处理接口等。
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll)
// 用于启用 HTTP 基本认证,withDefaults() 表示使用默认的 HTTP 基本认证配置。
.httpBasic(withDefaults());
return http.build();
}
}创建一个简单的控制器,提供 /hello 接口,调用该接口仅仅返回固定字符串“Hello, Spring Security!”,如下:
package com.hxstrive.spring_security.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 简单控制器
* @author hxstrive.com
*/
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Security!";
}
}在项目入口类上,通过 @MapperScan 注解设置 MyBatis Mapper 的位置,如下:
package com.hxstrive.spring_security;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
/**
* 入口类
* @author hxstrive.com
*/
@SpringBootApplication
@EnableWebSecurity
@MapperScan(basePackages = {"com.hxstrive.spring_security.mapper"})
public class SpringSecurityDemo3Application {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityDemo3Application.class, args);
}
}在 IDE 中运行 Spring Boot 应用程序的主类(通常是带有 @SpringBootApplication 注解的类),启动日志如下:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.9)
...
2025-05-07T16:16:02.587+08:00 INFO 22416 --- [spring_security_demo3] [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
...项目启动成功,端口为 8080。
打开浏览器,访问 http://localhost:8080/hello 地址,由于所有请求都需要身份验证,你会看到一个登录页面,如下图:

输入用户名(zhangsan)和密码(123456),如下图:

点击“Sign in”按钮,登录系统,访问 /hello 接口,如下图:

查看日志信息,如下:
loadUserByUsername(String username) :: zhangsan
2025-05-07T16:19:11.226+08:00 INFO 22416 --- [spring_security_demo3] [nio-8080-exec-5] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-05-07T16:19:12.068+08:00 INFO 22416 --- [spring_security_demo3] [nio-8080-exec-5] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@8cf13a9
2025-05-07T16:19:12.073+08:00 INFO 22416 --- [spring_security_demo3] [nio-8080-exec-5] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
loadUserByUsername(String username) :: zhangsan明显调用了自定义的“CustomUserDetailsService”类的“loadUserByUsername()”方法。