基于RBAC模型的后台权限管理系统

在这里插入图片描述

项目概述

这是一个基于 Spring Boot + Spring Security + MyBatis-Plus 的企业级后台权限管理系统,实现了完整的用户认证、授权、会话管理和动态权限控制功能。

技术架构

核心技术栈
  • 后端框架: Spring Boot 2.7.6
  • 安全框架: Spring Security 5.7.5
  • 持久层: MyBatis-Plus 3.5.3.1
  • 数据库: MySQL 8.0
  • 缓存/会话: Redis
  • 模板引擎: Thymeleaf 3.0.15
  • 开发语言: Java 17
主要功能模块
1. 身份认证模块
  • ✅ 表单登录认证
  • ✅ 验证码校验(图片验证码)
  • ✅ Remember-Me 记住我功能(Token 持久化到数据库)
  • ✅ 自定义登录成功/失败处理器
2. 权限管理模块
  • 动态权限控制: 基于 FilterInvocationSecurityMetadataSourceAccessDecisionManager 实现运行时权限加载
  • ✅ RBAC 模型: 用户-角色-权限三层权限体系
  • ✅ URL 级别权限控制
  • ✅ 自定义权限不足处理 handler
3. 会话管理
  • ✅ Session 存储到 Redis(支持分布式部署)
  • ✅ Session 超时时间: 600 秒(10 分钟)
  • ✅ 退出登录自动清理 Token
4. 业务管理模块
  • 👤 用户管理: 用户增删改查、角色分配
  • 🎭 角色管理: 角色增删改查、权限关联
  • 🔐 权限管理: 权限增删改查、动态刷新
  • 📦 商品管理: 商品 CRUD、显示/隐藏控制
5. 跨域支持
  • ✅ 完整的 CORS 配置
  • ✅ 支持携带凭证(Cookie、Authorization)
  • ✅ 预检请求缓存 1 小时

系统配置

服务配置
  • 端口: 8080
  • Session 超时: 600 秒
数据库配置
  • 类型: MySQL 8.0
  • 地址: 127.0.0.1:3306
  • 数据库名: security_db
Redis 配置
  • 地址: 127.0.0.1:6379
  • 数据库: DB 1
  • 用途: Session 共享、权限缓存
模板引擎
  • 引擎: Thymeleaf
  • 模式: LEGACYHTML5
  • 缓存: 关闭(开发环境)
  • 编码: UTF-8

核心特性

🔒 安全性
  1. 密码加密: BCrypt 强哈希算法
  2. CSRF 防护: 已禁用(根据业务需求)
  3. XSS 防护: Thymeleaf 自动转义
  4. 点击劫持防护: 同源 iframe 限制
⚡ 性能优化
  1. 静态资源忽略: CSS/JS/Images 不经过 Security 过滤器
  2. Redis 会话: 减轻服务器内存压力
  3. 动态权限缓存: 减少数据库查询次数
🎯 可扩展性
  1. 动态权限: 新增权限无需重启应用
  2. 模块化设计: Handler、Filter、Service 分层清晰
  3. RESTful API: 支持前后端分离扩展

适用场景

  • 🏢 企业内部管理系统
  • 🎓 学习 Spring Security 完整实践
  • 🔧 RBAC 权限模型参考实现
  • 🌐 分布式会话管理示例

项目结构

security-management/
├── config/              # 安全配置类
├── controller/          # 控制器层
├── pojo/              # 实体类
├── handler/             # 认证/授权处理器
├── filter/              # 自定义过滤器
├── service/             # 业务逻辑层
├── mapper/              # 数据访问层
└── resources/
    └── templates/       # Thymeleaf 模板

核心实现方案

RBAC模型数据库设计

  • 用户表sys_user包含usernamepassword(BCrypt加密)、status等字段
  • 角色表sys_role定义角色编码和名称
  • 权限表sys_permission存储权限标识和描述
  • 关联表sys_user_rolesys_role_permission建立多对多关系
#数据库设计实现
#用户表(sys_user)
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密存储',
    status TINYINT DEFAULT 1 COMMENT '0-禁用 1-启用',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

#角色表(sys_role)
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_code VARCHAR(50) NOT NULL UNIQUE COMMENT '角色编码',
    role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

#权限表(sys_permission)
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    perm_code VARCHAR(100) NOT NULL UNIQUE COMMENT '权限标识',
    perm_desc VARCHAR(200) COMMENT '权限描述',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);

#关联关系实现
#用户-角色关联表(sys_user_role)
CREATE TABLE sys_user_role (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES sys_user(id),
    FOREIGN KEY (role_id) REFERENCES sys_role(id)
);

#角色-权限关联表(sys_role_permission)
CREATE TABLE sys_role_permission (
    role_id BIGINT NOT NULL,
    perm_id BIGINT NOT NULL,
    PRIMARY KEY (role_id, perm_id),
    FOREIGN KEY (role_id) REFERENCES sys_role(id),
    FOREIGN KEY (perm_id) REFERENCES sys_permission(id)
);

典型查询示例
查询用户权限列表

SELECT DISTINCT p.perm_code 
FROM sys_user u
JOIN sys_user_role ur ON u.id = ur.user_id
JOIN sys_role_permission rp ON ur.role_id = rp.role_id
JOIN sys_permission p ON rp.perm_id = p.id
WHERE u.username = ? AND u.status = 1;

索引优化
在username、role_code和perm_code字段上建立唯一索引
在关联表的外键字段上建立普通索引以提高连接查询性能

CREATE INDEX idx_user_role ON sys_user_role(user_id, role_id);
CREATE INDEX idx_role_perm ON sys_role_permission(role_id, perm_id);

BCrypt密码加密示例

String encodedPassword = BCrypt.hashpw(rawPassword, BCrypt.gensalt());
// 验证密码
boolean matches = BCrypt.checkpw(rawPassword, encodedPassword);

动态权限控制实现

自定义元数据源

/**
 * @author weh
 * @version 1.0
 * @description: 自定义元数据源
 * @date 2026/5/11 22:35
 */
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
    @Autowired
    private PermissionService permissionService;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) {
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        List<Permission> permissionList = permissionService.listAll();
        for (Permission permission : permissionList) {
            if (new AntPathRequestMatcher(permission.getUrl()).matches(
                ((FilterInvocation) object).getRequest())) {
                return SecurityConfig.createList(permission.getPermissionCode());
            }
        }
        return null;
    }
}

安全配置关键代码

自定义的UserDetailsService接口实现类
用于获取用户信息

package com.weh.service.impl;

import com.weh.pojo.Permission;
import com.weh.pojo.User;
import com.weh.service.PermissionService;
import com.weh.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * @author weh
 * @version 1.0
 * @description: 这里是自定义的MyUserDetailsService,用于获取用户信息
 * 通过RBAC模型进行权限控制
 * @date 2026/5/7 22:49
 */
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private UserService userService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("====="+username);
        User user = userService.findByUsername(username);
        if (Objects.isNull( user)){
            throw new UsernameNotFoundException("用户不存在");
        }
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        List<Permission> permissionList = permissionService.findByUserId(user.getId());
        // 通过用户id查询权限列表,然后添加权限标签
        for (Permission permission : permissionList){
            authorities.add(new SimpleGrantedAuthority(permission.getPermissionTag()));
        }
        // 创建UserDetails对象,这里只是作为实例演示,可自行重写
        UserDetails userDetails = new org.springframework.security.core.userdetails.User
                (username,user.getPassword(),//noop不使用密码加密 , bcrypt使用加密算法
                        true,// 用户是否启用
                        true,// 用户是否过期
                        true,// 用户凭证是否过期
                        true,// 用户是否锁定
                        authorities);
        return userDetails;
    }
}

Spring Security配置类

package com.weh.config;

import com.weh.filter.ValidateFilter;
import com.weh.handler.*;
import com.weh.service.impl.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.sql.DataSource;

/**
 * Spring Security 安全配置类
 * <p>
 * 负责配置应用的身份认证、授权、跨域、会话管理等安全策略。
 * 采用动态权限管理,支持运行时从数据库加载权限配置。
 * </p>
 *
 * @author weh
 * @version 1.0
 * @date 2026/5/7 21:37
 */
@Configuration
//@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法权限控制,比如@PreAuthorize , @PostAuthorize ,@PreFilter , @PostFilter
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MyUserDetailsService myUserDetailsService;


    @Autowired
    private MyAuthenticationFailureHandler  myAuthenticationFailureHandler;
    @Autowired
    private MyAuthenticationSuccessHandler  myAuthenticationSuccessHandler;
    @Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    @Autowired
    private ValidateFilter validateFilter;

    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;


    /**
     * 配置密码编码器 Bean
     * <p>
     * 使用 BCrypt 强哈希算法对密码进行加密存储和验证。
     * </p>
     *
     * @return PasswordEncoder 实例
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 配置身份认证管理器
     * <p>
     * 指定自定义的用户详情服务(MyUserDetailsService)和密码编码器,
     * 用于在登录时从数据库加载用户信息并验证密码。
     * </p>
     *
     * @param auth AuthenticationManagerBuilder 构建器
     * @throws Exception 配置异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService)// 使用自定义用户认证
                .passwordEncoder(passwordEncoder()); // 密码编码器
    }


    /**
     * 配置忽略静态资源的安全过滤
     * <p>
     * 将 CSS、图片、JS、验证码等静态资源路径排除在 Security 过滤器链之外,
     * 提高性能并避免不必要的认证检查。
     * </p>
     *
     * @param web WebSecurity 构建器
     */
    @Override
    public void configure(WebSecurity web) {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/css/**", "/images/**", "/js/**","/code/**");
    }


    /**
     * 配置 HTTP 安全策略
     * <p>
     * 包括验证码过滤器、动态权限管理、表单登录、登出、记住我、跨域等功能。
     * </p>
     *
     * @param http HttpSecurity 构建器
     * @throws Exception 配置异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

    //===================================================================================
    // 在用户名密码认证过滤器之前添加验证码校验过滤器
    http.addFilterBefore(validateFilter, UsernamePasswordAuthenticationFilter.class);


    // ==================================================================================================
    // 权限控制(必须要放到anyRequest().authenticated()之前的位置,不然报错)
    //    http.authorizeRequests().antMatchers("/user/**").hasRole("ADMIN"); // /user/** 路径下的所有请求,需要ADMIN角色才能访问
    //    // url权限控制, 设置多个角色用, 多个ip用and连接; 这里配置了ADMIN / PRODUCT角色并且IP为127.0.0.1的ip才能访问
    //    http.authorizeRequests().antMatchers("/product/**").access("hasAnyRole('ADMIN','PRODUCT')and hasIpAddress('127.0.0.1')");
    //    http.authorizeRequests().antMatchers("/user/**").access("@myAuthorizationConfig.check(authentication,request)");

    //hasRole和hasAuthority的区别就是权限标签,
    // hasRole是角色权限,hasAuthority是权限标签
    //hasRole("ADMIN")  --用户授予 new SimpleGrantedAuthority("ROLE_ADMIN")
    //hasAuthority("user:getAll")  --用户授予 new SimpleGrantedAuthority("user:getAll")

    // 查询所有权限列表
    //    List<Permission> permissions = permissionService.list();
    //    // 设置每个接口url进行权限控制,通过权限标签permissionTag进行权限控制
    //    for (Permission permission : permissions){
    //        http.authorizeRequests().antMatchers(permission.getPermissionUrl())
    //                .hasAuthority(permission.getPermissionTag());
    //    }


   // 配置动态权限管理:通过自定义的元数据源和决策管理器实现运行时权限控制
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                        fsi.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        fsi.setAccessDecisionManager(myAccessDecisionManager);
                        return fsi;
                    }
                })
                .and().exceptionHandling().accessDeniedHandler(myAccessDeniedHandler); // 添加权限访问处理类






    // ===================================================================================

        //用HttpBasic认证(不安全)
        //        http.httpBasic()
        //                .and().authorizeRequests() // 配置权限,默认所有请求都需要认证
        //                .anyRequest()               // 所有请求
        //                .authenticated();        // 需要认证


        // 用配置表单认证(较为安全)
        http.formLogin()
                // 自定义登录页面(用static文件作为静态资源是可以直接写login.html),但是项目中用的是templates模板,路径需改为映射的路径
                .loginPage("/toLoginPage")
                .loginProcessingUrl("/login")   // 登录处理接口url
                .usernameParameter("username") // 用户名参数名
                .passwordParameter("password") // 密码参数名
                .defaultSuccessUrl("/")         // 登录成功后默认跳转的页面 / 自动登录后跳转到主页
                .successHandler(myAuthenticationSuccessHandler) // 引入自定义登录成功处理器
                .failureHandler(myAuthenticationFailureHandler) // 引入自定义登录失败处理器
                .and()
                .logout().logoutUrl("/logout")// 登出接口url,如果有remember-me功能,系统会自动将token进行删除
                .logoutSuccessHandler(myLogoutSuccessHandler) // 引入自定义登出成功处理器
                .and()
                .rememberMe()// 开启记住我功能
                .rememberMeParameter("remember-me")// 记住我参数名
                .tokenValiditySeconds(60 * 60 * 24 * 7)// token有效时间,默认14天
                .tokenRepository(getPersistentTokenRepository()) // 持久化token,将token保存到数据库中persistent_logins表
                .and()
                .authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面和验证码接口
                .anyRequest()
                .authenticated();


        //加载同源域名下iframe页面
        http.headers().frameOptions().sameOrigin();

        // ==================================================================================================
        // session会话设置
//        http.sessionManagement()
//                .invalidSessionUrl("/toLoginPage") // session失效后跳转的页面
//                .maximumSessions(1)// 设置session最大会话数量,1同一时间只能有一个用户登录,互踢
//                .maxSessionsPreventsLogin(true) //当达到最大数量后,不允许再登录
//                .expiredUrl("/toLoginPage"); // session过期后跳转的页面


        // 禁用csrf,若开启csrf,则需要配置csrfTokenRepository, 否则会报403错误
         http.csrf().disable();
        // 开启跨域支持,允许跨域
        http.cors().configurationSource(corsConfigurationSource());
    }



    /**
     * 配置持久化 Token 仓库(记住我功能)
     * <p>
     * 将 Remember-Me 的 Token 存储到数据库中,支持跨会话保持登录状态。
     * 首次启动时可设置 setCreateTableOnStartup(true) 自动创建 persistent_logins 表。
     * </p>
     *
     * @return PersistentTokenRepository 实例
     */
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);// 设置数据源
        //启动时帮助我们自动创建一张表persistent_logins, 第一次启动设置true 第二次启动设置false或者注释
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }


    @Autowired
    private CorsConfiguration corsConfiguration;

    /**
     * 配置跨域资源共享(CORS)信息源
     * <p>
     * 允许所有来源、方法、请求头的跨域请求,并支持携带凭证(Cookie、Authorization 等)。
     * 预检请求缓存时间为 1 小时,减少浏览器 OPTIONS 请求次数。
     * </p>
     *
     * @return CorsConfigurationSource 跨域配置源
     */
    private CorsConfigurationSource corsConfigurationSource() {
        // 允许跨域的站点(使用 allowedOriginPattern 支持通配符,兼容 allowCredentials)
        corsConfiguration.addAllowedOriginPattern("*");
       //用spring boot 2.4.0以上版本不支持
        //  corsConfiguration.addAllowedOrigin("*");
        // 允许跨域的 HTTP 方法(GET, POST, PUT, DELETE, OPTIONS 等)
        corsConfiguration.addAllowedMethod("*");
        // 允许跨域的请求头(Content-Type, Authorization 等)
        corsConfiguration.addAllowedHeader("*");
        // 允许携带凭证(cookie、session、Authorization 头等)
        corsConfiguration.setAllowCredentials(true);
        // 预检请求(OPTIONS)的缓存时间(秒),减少预检请求次数
        corsConfiguration.setMaxAge(3600L);
        // 创建基于 URL 的跨域配置源
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        // 对所有 URL 路径应用此跨域配置
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return urlBasedCorsConfigurationSource;
    }
}

会话管理配置

RedisSession配置

spring:
  session:
    store-type: redis
    timeout: 600s
    redis:
      namespace: spring:session
  redis:
    host: 127.0.0.1
    port: 6379
    database: 1

前端权限控制方案

Thymeleaf安全表达式

<div sec:authorize="hasAuthority('user:add')">
    <button class="btn-add">新增用户</button>
</div>

性能优化措施

权限缓存实现

@Cacheable(value = "permissionCache", key = "'all_permissions'")
public List<Permission> listAll() {
    return baseMapper.selectList(null);
}

@CacheEvict(value = "permissionCache", key = "'all_permissions'")
public void clearPermissionCache() {
    // 清空缓存
}

异常处理机制

自定义AccessDeniedHandler

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, 
                      HttpServletResponse response,
                      AccessDeniedException e) {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(
            Result.error(403, "权限不足")));
    }
}

后台管理系统登录
在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐