Spring Security+MyBatis实现从数据库动态查询权限的完整实现方案

 更新时间:2026年04月12日 15:09:00   投稿:bairu  
文章详细介绍了SpringSecurity从数据库动态查询权限的实现方案,包括核心接口、数据库表设计、MyBatis查询权限、UserDetailsService实现、SpringSecurity配置、Controller使用及常见问题优化等内容,该方案生产可用,灵活且遵循SpringSecurity接口设计标准

一、整体思路

Spring Security 的核心接口:

  • UserDetailsService:根据用户名加载用户信息(密码、权限等)
  • UserDetails:用户对象,包含密码和 GrantedAuthority
  • GrantedAuthority:权限标识(如 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数据库动态查询权限的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot中使用Session共享实现分布式部署的示例代码

    SpringBoot中使用Session共享实现分布式部署的示例代码

    这篇文章主要介绍了SpringBoot中使用Session共享实现分布式部署的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • Java获取彩色图像中的主色彩的实例代码

    Java获取彩色图像中的主色彩的实例代码

    这篇文章主要介绍了Java获取彩色图像中的主色彩的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java关键字volatile详析

    Java关键字volatile详析

    这篇文章主要介绍了Java关键字volatile,volatile关键字可以说是Java虚拟机提供的最轻量级的同步机制,但对于为什么它只能保证可见性,不保证原子性,它又是如何禁用指令重排的,还有很多同学没彻底理解,文章会让大家牢掌握一个Java核心知识点
    2022-01-01
  • Spring Boot统一处理全局异常的实战教程

    Spring Boot统一处理全局异常的实战教程

    最近在做项目时需要对异常进行全局统一处理,所以下面这篇文章主要给大家介绍了关于Spring Boot统一处理全局异常的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2021-12-12
  • 修改Maven settings.xml 后配置未生效的解决

    修改Maven settings.xml 后配置未生效的解决

    这篇文章主要介绍了修改Maven settings.xml 后配置未生效的解决,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Mybatis中如何使用sum对字段求和

    Mybatis中如何使用sum对字段求和

    这篇文章主要介绍了Mybatis中如何使用sum对字段求和,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • java若依框架集成redis缓存详解

    java若依框架集成redis缓存详解

    今天小编就为大家分享一篇关于java若依框架集成redis缓存的实现,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2021-08-08
  • JVM默认时区为:Asia/Shanghai与java程序中GMT+08不一致异常

    JVM默认时区为:Asia/Shanghai与java程序中GMT+08不一致异常

    这篇文章主要介绍了JVM默认时区为:Asia/Shanghai与java程序中GMT+08不一致异常问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Java异常基础知识解析

    Java异常基础知识解析

    这篇文章主要介绍了Java异常基础知识解析,具有一定借鉴价值,需要的朋友可以资参考下。
    2017-12-12
  • Java 7大常见排序方法实例详解

    Java 7大常见排序方法实例详解

    这篇文章主要通过实例介绍了Java中常用的7种排序方法,需要的朋友可以参考下
    2017-04-04

最新评论