Spring Security+MyBatis实现从数据库动态查询权限的完整实现方案
投稿:bairu
文章详细介绍了SpringSecurity从数据库动态查询权限的实现方案,包括核心接口、数据库表设计、MyBatis查询权限、UserDetailsService实现、SpringSecurity配置、Controller使用及常见问题优化等内容,该方案生产可用,灵活且遵循SpringSecurity接口设计标准
一、整体思路
Spring Security 的核心接口:
UserDetailsService:根据用户名加载用户信息(密码、权限等)UserDetails:用户对象,包含密码和GrantedAuthorityGrantedAuthority:权限标识(如ROLE_ADMIN或system:user:delete)
流程:
登录 → UserDetailsService.loadUserByUsername() → 查数据库 → 返回 UserDetails → Security自动比对密码 → 把权限存入SecurityContext。
二、数据库表设计(最简但完整)
-- 用户表 CREATE TABLE `user` ( `id` int PRIMARY KEY AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, -- BCrypt加密 `enabled` tinyint DEFAULT 1 ); -- 角色表 CREATE TABLE `role` ( `id` int PRIMARY KEY AUTO_INCREMENT, `role_code` varchar(50) NOT NULL -- ROLE_ADMIN, ROLE_USER ); -- 权限表(细粒度权限) CREATE TABLE `permission` ( `id` int PRIMARY KEY AUTO_INCREMENT, `perm_code` varchar(100) NOT NULL -- user:add, user:delete, order:view ); -- 用户-角色关联 CREATE TABLE `user_role` ( `user_id` int, `role_id` int ); -- 角色-权限关联 CREATE TABLE `role_permission` ( `role_id` int, `perm_id` int );
示例数据:
-- 用户 admin / 123456(实际用BCrypt) INSERT INTO `user` VALUES (1, 'admin', '$2a$10$NkMwpJ7UoWjQlqWjQlqWjQ', 1); -- 角色 INSERT INTO `role` VALUES (1, 'ROLE_ADMIN'), (2, 'ROLE_USER'); -- 权限 INSERT INTO `permission` VALUES (1, 'user:list'), (2, 'user:delete'); -- 管理员拥有所有角色和权限 INSERT INTO `user_role` VALUES (1,1); INSERT INTO `role_permission` VALUES (1,1),(1,2);
三、MyBatis 查询权限
1. 实体类
public class User {
private Integer id;
private String username;
private String password;
private Boolean enabled;
// getter/setter
}2. Mapper 接口
@Mapper
public interface UserMapper {
@Select("SELECT id, username, password, enabled FROM user WHERE username = #{username}")
User findByUsername(String username);
@Select("SELECT r.role_code FROM user_role ur " +
"JOIN role r ON ur.role_id = r.id " +
"WHERE ur.user_id = #{userId}")
List<String> findRolesByUserId(Integer userId);
@Select("SELECT p.perm_code FROM user_role ur " +
"JOIN role_permission rp ON ur.role_id = rp.role_id " +
"JOIN permission p ON rp.perm_id = p.id " +
"WHERE ur.user_id = #{userId}")
List<String> findPermissionsByUserId(Integer userId);
}四、核心:实现 UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查用户
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 2. 查角色(ROLE_xxx)
List<String> roles = userMapper.findRolesByUserId(user.getId());
// 3. 查权限(user:add 等)
List<String> permissions = userMapper.findPermissionsByUserId(user.getId());
// 4. 合并权限 + 角色
// 注意:Spring Security 中角色需要加 ROLE_ 前缀
Set<GrantedAuthority> authorities = new HashSet<>();
for (String role : roles) {
// 如果role已经是 ROLE_ADMIN 格式,直接加;否则自动补
if (role.startsWith("ROLE_")) {
authorities.add(new SimpleGrantedAuthority(role));
} else {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
}
for (String perm : permissions) {
authorities.add(new SimpleGrantedAuthority(perm));
}
// 5. 返回 UserDetails
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getEnabled(), // 是否启用
true, // 账号未过期
true, // 凭证未过期
true, // 账号未锁定
authorities
);
}
}五、Spring Security 配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 密码加密
}
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return http.getSharedObject(AuthenticationManagerBuilder.class)
.authenticationProvider(provider)
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 公开接口
.requestMatchers("/login", "/register").permitAll()
// 权限控制:需要具体权限
.requestMatchers("/user/list").hasAuthority("user:list")
.requestMatchers("/user/delete").hasAuthority("user:delete")
// 角色控制
.requestMatchers("/admin/**").hasRole("ADMIN")
// 其他请求需要认证
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
)
.logout(LogoutConfigurer::permitAll)
.csrf(csrf -> csrf.disable()); // 根据实际情况开启
return http.build();
}
}六、在 Controller 中使用
@RestController
public class UserController {
@GetMapping("/user/list")
public String listUsers() {
return "用户列表(需要 user:list 权限)";
}
@GetMapping("/user/delete")
public String deleteUser() {
return "删除用户(需要 user:delete 权限)";
}
@GetMapping("/current-permissions")
public Authentication getCurrentAuth() {
return SecurityContextHolder.getContext().getAuthentication();
}
}七、常见问题与优化
1. 缓存权限查询
不要在每次请求时重复查数据库(Security 本身一次登录后权限存session)。
如果一定要动态刷新权限,可以:
// 修改权限后,重新加载 SecurityContextHolder.getContext().setAuthentication(updatedAuth);
2. 使用 @PreAuthorize 更优雅
@PreAuthorize("hasAuthority('user:delete')")
@DeleteMapping("/user/{id}")
public String delete(@PathVariable Long id) {
return "删除成功";
}需要开启:
@EnableGlobalMethodSecurity(prePostEnabled = true)
3. 性能优化:一次查询返回用户+角色+权限
用 MyBatis 联表或 ResultMap 一次性查出来,避免 N+1 问题。
<resultMap id="UserWithAuth" type="com.example.User">
<id column="id" property="id"/>
<collection property="roles" ofType="string" select="..."/>
</resultMap>八、总结:这套方案的特点
| 方面 | 说明 |
|---|---|
| ✅ 生产可用 | 绝大多数项目真实采用 |
| ✅ 灵活 | 权限可存数据库,随时调整 |
| ✅ 标准 | 完全遵循 Spring Security 接口设计 |
| ⚠️ 注意 | 密码必须用 BCrypt 等单向加密 |
| ⚠️ 注意 | 角色必须加 ROLE_ 前缀(除非自定义) |
以上就是Spring Security+MyBatis实现从数据库动态查询权限的完整实现方案的详细内容,更多关于Spring Security MyBatis数据库动态查询权限的资料请关注脚本之家其它相关文章!
