本章节将介绍如何通过 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 primary key auto_increment, name varchar(20) ); insert into role values(1,'管理员'); insert into role values(2,'普通用户'); -- 角色用户表 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()”方法。