本章节将介绍如何通过 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()”方法。