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数据库动态查询权限的资料请关注脚本之家其它相关文章!

相关文章

  • Java程序的逻辑控制和方法详解

    Java程序的逻辑控制和方法详解

    这篇文章主要介绍了Java程序的逻辑控制和方法详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • String.intern()作用与常量池关系示例解析

    String.intern()作用与常量池关系示例解析

    这篇文章主要为大家介绍了String.intern()作用与常量池关系示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 在 Spring Boot 中实现异常处理最佳实践

    在 Spring Boot 中实现异常处理最佳实践

    本文介绍如何在Spring Boot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧
    2025-04-04
  • Spring Bean创建和循环依赖

    Spring Bean创建和循环依赖

    这篇文章主要介绍了Spring Bean创建和循环依赖,讲述了Spring容器中 Bean 的创建过程已经主要的方法,另外也着重分析了循环依赖的问题,需要的小伙伴可以参考一下
    2022-05-05
  • Java transient关键字与序列化操作实例详解

    Java transient关键字与序列化操作实例详解

    这篇文章主要介绍了Java transient关键字与序列化操作,结合实例形式详细分析了java序列化操作相关实现方法与操作注意事项,需要的朋友可以参考下
    2019-09-09
  • Mybatis实现一对一查询映射处理

    Mybatis实现一对一查询映射处理

    MyBatis是一种流行的Java持久化框架,它提供了灵活而强大的查询映射功能,本文主要介绍了Mybatis实现一对一查询映射处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • 百度Java面试题 前200页精选(中)

    百度Java面试题 前200页精选(中)

    这篇文章主要为大家分享了Java面试资源中篇,百度“Java面试题”前200页都在这里了,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • SpringCloud Feign的使用代码实例

    SpringCloud Feign的使用代码实例

    这篇文章主要介绍了SpringCloud Feign的使用代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 解决Java的InputMismatchException异常

    解决Java的InputMismatchException异常

    这篇文章介绍了解决Java的InputMismatchException异常的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • java 实现计数排序和桶排序实例代码

    java 实现计数排序和桶排序实例代码

    这篇文章主要介绍了java 实现计数排序和桶排序实例代码的相关资料,需要的朋友可以参考下
    2017-02-02

最新评论