从0到1搭建企业级权限管理系统:Spring Boot + JWT + RBAC实战指南

引言:为什么权限管理是企业系统的基石

在任何企业级应用中,权限管理都是不可或缺的核心模块。一个完善的权限管理系统不仅要解决"谁能访问什么"的问题,还要兼顾安全性、可扩展性和易用性。

本文将带你从0到1搭建一个基于Spring Boot + JWT + RBAC的企业级权限管理系统,涵盖架构设计、数据库建模、核心代码实现、安全策略和性能优化等方方面面。

一、系统架构设计

1.1 整体架构

复制代码
┌─────────────────────────────────────────────────────────┐
│                      客户端层                            │
│  Web前端 / 移动端 / 第三方应用                            │
└─────────────────────┬───────────────────────────────────┘
                      │ HTTP/HTTPS
┌─────────────────────▼───────────────────────────────────┐
│                     网关层                               │
│  统一认证 / 限流 / 日志 / 跨域                            │
└─────────────────────┬───────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────┐
│                   应用服务层                             │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ 用户服务 │  │ 角色服务 │  │ 权限服务 │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ 认证服务 │  │ 日志服务 │  │ 菜单服务 │              │
│  └──────────┘  └──────────┘  └──────────┘              │
└─────────────────────┬───────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────┐
│                   数据访问层                             │
│  MyBatis / JPA / Redis                                   │
└─────────────────────┬───────────────────────────────────┘
                      │
┌─────────────────────▼───────────────────────────────────┐
│                   数据存储层                             │
│  MySQL / Redis                                           │
└─────────────────────────────────────────────────────────┘

1.2 技术栈选型

层次 技术 版本
后端框架 Spring Boot 3.2.x
安全框架 Spring Security 6.x
认证方式 JWT (JSON Web Token) 0.12.x
ORM框架 MyBatis-Plus 3.5.x
缓存 Redis -
数据库 MySQL 8.0+
工具类 Hutool / Lombok -

二、RBAC模型详解

2.1 什么是RBAC

RBAC(Role-Based Access Control,基于角色的访问控制)是目前最主流的权限管理模型。其核心思想是:

  • 用户(User):系统的使用者
  • 角色(Role):权限的集合,代表一类职责
  • 权限(Permission):对资源的操作权限
  • 关系:用户关联角色,角色关联权限

2.2 RBAC的优势

  1. 简化管理:通过角色间接授权,避免直接给用户授权的管理复杂度
  2. 灵活扩展:新增权限只需修改角色权限关联,不影响用户
  3. 职责清晰:角色对应岗位职责,便于理解和维护
  4. 支持继承:可实现角色继承,满足复杂组织需求

2.3 RBAC数据模型

复制代码
用户 (sys_user) ──┐
                  ├── 用户角色关联 (sys_user_role) ──┐
角色 (sys_role) ──┘                                  ├── 角色权限关联 (sys_role_menu)
                                                     │
菜单/权限 (sys_menu) ────────────────────────────────┘

三、数据库设计

3.1 用户表

sql 复制代码
CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码(加密)',
  `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除:0-未删除,1-已删除',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
  `update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

3.2 角色表

sql 复制代码
CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_code` varchar(50) NOT NULL COMMENT '角色编码',
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `description` varchar(255) DEFAULT NULL COMMENT '描述',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

3.3 菜单/权限表

sql 复制代码
CREATE TABLE `sys_menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
  `parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID',
  `menu_name` varchar(50) NOT NULL COMMENT '菜单名称',
  `menu_type` tinyint(1) NOT NULL COMMENT '类型:0-目录,1-菜单,2-按钮',
  `path` varchar(200) DEFAULT NULL COMMENT '路由路径',
  `component` varchar(200) DEFAULT NULL COMMENT '组件路径',
  `permission` varchar(100) DEFAULT NULL COMMENT '权限标识(如:user:list)',
  `icon` varchar(50) DEFAULT NULL COMMENT '图标',
  `sort_order` int(11) DEFAULT '0' COMMENT '排序',
  `visible` tinyint(1) DEFAULT '1' COMMENT '是否可见:0-隐藏,1-显示',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单权限表';

3.4 用户角色关联表

sql 复制代码
CREATE TABLE `sys_user_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_role` (`user_id`, `role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

3.5 角色菜单关联表

sql 复制代码
CREATE TABLE `sys_role_menu` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_menu` (`role_id`, `menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色菜单关联表';

四、核心代码实现

4.1 项目结构

复制代码
src/main/java/com/example/auth/
├── config/               # 配置类
│   ├── SecurityConfig.java
│   ├── JwtConfig.java
│   └── MybatisPlusConfig.java
├── controller/           # 控制器
│   ├── AuthController.java
│   ├── UserController.java
│   ├── RoleController.java
│   └── MenuController.java
├── entity/               # 实体类
│   ├── SysUser.java
│   ├── SysRole.java
│   └── SysMenu.java
├── mapper/               # 数据访问层
│   ├── SysUserMapper.java
│   ├── SysRoleMapper.java
│   └── SysMenuMapper.java
├── service/              # 业务逻辑层
│   ├── AuthService.java
│   ├── UserService.java
│   ├── RoleService.java
│   └── MenuService.java
├── security/             # 安全相关
│   ├── JwtTokenProvider.java
│   ├── JwtAuthenticationFilter.java
│   └── CustomUserDetailsService.java
├── dto/                  # 数据传输对象
│   ├── LoginRequest.java
│   ├── LoginResponse.java
│   └── UserInfoDTO.java
└── common/               # 通用组件
    ├── Result.java
    └── Constants.java

4.2 JWT工具类

java 复制代码
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Map;

@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private long expiration;

    private SecretKey getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    /**
     * 生成Token
     */
    public String generateToken(Long userId, String username, Map<String, Object> claims) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);

        return Jwts.builder()
                .setSubject(username)
                .claim("userId", userId)
                .addClaims(claims)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 从Token中获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    /**
     * 从Token中获取用户ID
     */
    public Long getUserIdFromToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claims.get("userId", Long.class);
    }

    /**
     * 验证Token是否有效
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 刷新Token
     */
    public String refreshToken(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }
}

4.3 JWT认证过滤器

java 复制代码
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider tokenProvider;
    private final CustomUserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, 
                                   CustomUserDetailsService userDetailsService) {
        this.tokenProvider = tokenProvider;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                String username = tokenProvider.getUsernameFromToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(
                        new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

4.4 自定义UserDetailsService

java 复制代码
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.List;
import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final SysUserMapper userMapper;
    private final SysRoleMapper roleMapper;
    private final SysMenuMapper menuMapper;

    public CustomUserDetailsService(SysUserMapper userMapper, 
                                    SysRoleMapper roleMapper,
                                    SysMenuMapper menuMapper) {
        this.userMapper = userMapper;
        this.roleMapper = roleMapper;
        this.menuMapper = menuMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userMapper.selectByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在: " + username);
        }

        // 获取用户角色
        List<SysRole> roles = roleMapper.selectRolesByUserId(user.getId());
        
        // 获取用户权限
        List<String> permissions = menuMapper.selectPermissionsByUserId(user.getId());
        
        // 构建权限列表
        List<SimpleGrantedAuthority> authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                user.getStatus() == 1,  // 是否启用
                true, true, true,       // 账号是否过期等
                authorities
        );
    }
}

4.5 Spring Security配置

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    private final JwtTokenProvider tokenProvider;
    private final CustomUserDetailsService userDetailsService;

    public SecurityConfig(JwtTokenProvider tokenProvider, 
                         CustomUserDetailsService userDetailsService) {
        this.tokenProvider = tokenProvider;
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(tokenProvider, userDetailsService);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter(), 
                    UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

4.6 认证服务实现

java 复制代码
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class AuthService {

    private final AuthenticationManager authenticationManager;
    private final JwtTokenProvider tokenProvider;
    private final SysUserMapper userMapper;

    public AuthService(AuthenticationManager authenticationManager,
                       JwtTokenProvider tokenProvider,
                       SysUserMapper userMapper) {
        this.authenticationManager = authenticationManager;
        this.tokenProvider = tokenProvider;
        this.userMapper = userMapper;
    }

    /**
     * 用户登录
     */
    public LoginResponse login(LoginRequest request) {
        // 认证用户名密码
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        request.getUsername(),
                        request.getPassword()
                )
        );

        // 获取用户信息
        SysUser user = userMapper.selectByUsername(request.getUsername());

        // 构建JWT Claims
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", user.getId());
        claims.put("authorities", authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()));

        // 生成Token
        String token = tokenProvider.generateToken(user.getId(), user.getUsername(), claims);

        return new LoginResponse(
                token,
                user.getId(),
                user.getUsername(),
                user.getNickname()
        );
    }

    /**
     * 刷新Token
     */
    public String refreshToken(String oldToken) {
        return tokenProvider.refreshToken(oldToken);
    }
}

4.7 认证控制器

java 复制代码
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final AuthService authService;

    public AuthController(AuthService authService) {
        this.authService = authService;
    }

    @PostMapping("/login")
    public ResponseEntity<Result<LoginResponse>> login(@RequestBody LoginRequest request) {
        LoginResponse response = authService.login(request);
        return ResponseEntity.ok(Result.success(response));
    }

    @PostMapping("/refresh")
    public ResponseEntity<Result<String>> refreshToken(
            @RequestHeader("Authorization") String authorization) {
        String token = authorization.substring(7);
        String newToken = authService.refreshToken(token);
        return ResponseEntity.ok(Result.success(newToken));
    }

    @PostMapping("/logout")
    public ResponseEntity<Result<Void>> logout() {
        // 实际项目中可以将Token加入黑名单
        return ResponseEntity.ok(Result.success());
    }
}

4.8 权限注解与AOP

java 复制代码
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
    String[] value();
    Logical logical() default Logical.AND;

    enum Logical {
        AND, OR
    }
}
java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Aspect
@Component
public class PermissionAspect {

    @Around("@annotation(requirePermission)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, 
                                  RequirePermission requirePermission) throws Throwable {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null) {
            throw new SecurityException("未认证");
        }

        List<String> userPermissions = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());

        String[] requiredPermissions = requirePermission.value();
        Logical logical = requirePermission.logical();

        boolean hasPermission;
        if (logical == Logical.AND) {
            hasPermission = Arrays.stream(requiredPermissions)
                    .allMatch(userPermissions::contains);
        } else {
            hasPermission = Arrays.stream(requiredPermissions)
                    .anyMatch(userPermissions::contains);
        }

        if (!hasPermission) {
            throw new SecurityException("权限不足");
        }

        return joinPoint.proceed();
    }
}

五、核心业务实现

5.1 用户管理

java 复制代码
@Service
public class UserService {

    private final SysUserMapper userMapper;
    private final PasswordEncoder passwordEncoder;

    public UserService(SysUserMapper userMapper, PasswordEncoder passwordEncoder) {
        this.userMapper = userMapper;
        this.passwordEncoder = passwordEncoder;
    }

    public SysUser getUserById(Long id) {
        return userMapper.selectById(id);
    }

    public PageResult<SysUser> listUsers(UserQueryRequest request) {
        Page<SysUser> page = new Page<>(request.getPageNum(), request.getPageSize());
        return userMapper.selectPage(page, 
                Wrappers.<SysUser>lambdaQuery()
                        .like(StringUtils.hasText(request.getUsername()), 
                                SysUser::getUsername, request.getUsername())
                        .eq(request.getStatus() != null, SysUser::getStatus, request.getStatus())
                        .orderByDesc(SysUser::getCreateTime));
    }

    @Transactional
    public void createUser(SysUser user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        userMapper.insert(user);
    }

    @Transactional
    public void updateUser(SysUser user) {
        if (StringUtils.hasText(user.getPassword())) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
        userMapper.updateById(user);
    }

    @Transactional
    public void deleteUser(Long id) {
        userMapper.deleteById(id);
    }

    @Transactional
    public void assignRoles(Long userId, List<Long> roleIds) {
        // 先删除原有角色
        userRoleMapper.delete(Wrappers.<SysUserRole>lambdaQuery()
                .eq(SysUserRole::getUserId, userId));
        
        // 添加新角色
        if (roleIds != null && !roleIds.isEmpty()) {
            List<SysUserRole> userRoles = roleIds.stream()
                    .map(roleId -> new SysUserRole(userId, roleId))
                    .collect(Collectors.toList());
            userRoleMapper.insertBatchSomeColumn(userRoles);
        }
    }
}

5.2 角色管理

java 复制代码
@Service
public class RoleService {

    private final SysRoleMapper roleMapper;
    private final SysRoleMenuMapper roleMenuMapper;

    public RoleService(SysRoleMapper roleMapper, SysRoleMenuMapper roleMenuMapper) {
        this.roleMapper = roleMapper;
        this.roleMenuMapper = roleMenuMapper;
    }

    public List<SysRole> listRoles() {
        return roleMapper.selectList(null);
    }

    @Transactional
    public void createRole(SysRole role) {
        roleMapper.insert(role);
    }

    @Transactional
    public void updateRole(SysRole role) {
        roleMapper.updateById(role);
    }

    @Transactional
    public void deleteRole(Long id) {
        roleMapper.deleteById(id);
        // 删除角色菜单关联
        roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery()
                .eq(SysRoleMenu::getRoleId, id));
    }

    @Transactional
    public void assignMenus(Long roleId, List<Long> menuIds) {
        // 先删除原有菜单
        roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery()
                .eq(SysRoleMenu::getRoleId, roleId));
        
        // 添加新菜单
        if (menuIds != null && !menuIds.isEmpty()) {
            List<SysRoleMenu> roleMenus = menuIds.stream()
                    .map(menuId -> new SysRoleMenu(roleId, menuId))
                    .collect(Collectors.toList());
            roleMenuMapper.insertBatchSomeColumn(roleMenus);
        }
    }
}

5.3 菜单权限管理

java 复制代码
@Service
public class MenuService {

    private final SysMenuMapper menuMapper;

    public MenuService(SysMenuMapper menuMapper) {
        this.menuMapper = menuMapper;
    }

    /**
     * 获取菜单树
     */
    public List<MenuTreeDTO> getMenuTree() {
        List<SysMenu> allMenus = menuMapper.selectList(
                Wrappers.<SysMenu>lambdaQuery().orderByAsc(SysMenu::getSortOrder));
        return buildMenuTree(allMenus, 0L);
    }

    /**
     * 获取用户菜单
     */
    public List<MenuTreeDTO> getUserMenuTree(Long userId) {
        List<SysMenu> menus = menuMapper.selectMenusByUserId(userId);
        return buildMenuTree(menus, 0L);
    }

    private List<MenuTreeDTO> buildMenuTree(List<SysMenu> menus, Long parentId) {
        return menus.stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(menu -> {
                    MenuTreeDTO node = new MenuTreeDTO(menu);
                    node.setChildren(buildMenuTree(menus, menu.getId()));
                    return node;
                })
                .collect(Collectors.toList());
    }
}

六、安全策略

6.1 密码安全

java 复制代码
@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12); // 强度因子
    }
}

6.2 Token安全

yaml 复制代码
jwt:
  secret: your-256-bit-secret-key-here-must-be-long-enough
  expiration: 7200000  # 2小时
  refresh-expiration: 604800000  # 7天

6.3 防重放攻击

java 复制代码
@Component
public class ReplayAttackFilter extends OncePerRequestFilter {

    private final RedisTemplate<String, String> redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");
        String nonce = request.getHeader("X-Nonce");
        String timestamp = request.getHeader("X-Timestamp");

        // 检查时间戳
        if (timestamp != null) {
            long requestTime = Long.parseLong(timestamp);
            if (System.currentTimeMillis() - requestTime > 300000) { // 5分钟
                response.sendError(400, "请求已过期");
                return;
            }
        }

        // 检查nonce
        if (nonce != null) {
            String key = "nonce:" + nonce;
            Boolean isNew = redisTemplate.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.MINUTES);
            if (Boolean.FALSE.equals(isNew)) {
                response.sendError(400, "重复请求");
                return;
            }
        }

        filterChain.doFilter(request, response);
    }
}

6.4 接口限流

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int maxRequests() default 100;
    long timeWindow() default 60;
}

七、缓存优化

7.1 权限缓存

java 复制代码
@Service
public class PermissionCacheService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final SysMenuMapper menuMapper;

    private static final String PERMISSION_CACHE_KEY = "user:permissions:";
    private static final long CACHE_EXPIRE = 30; // 30分钟

    public List<String> getUserPermissions(Long userId) {
        String key = PERMISSION_CACHE_KEY + userId;
        
        // 先查缓存
        List<String> permissions = (List<String>) redisTemplate.opsForValue().get(key);
        if (permissions != null) {
            return permissions;
        }

        // 缓存未命中,查数据库
        permissions = menuMapper.selectPermissionsByUserId(userId);
        
        // 写入缓存
        redisTemplate.opsForValue().set(key, permissions, CACHE_EXPIRE, TimeUnit.MINUTES);
        
        return permissions;
    }

    public void evictUserPermissions(Long userId) {
        String key = PERMISSION_CACHE_KEY + userId;
        redisTemplate.delete(key);
    }
}

7.2 Token黑名单

java 复制代码
@Service
public class TokenBlacklistService {

    private final RedisTemplate<String, String> redisTemplate;
    private final JwtTokenProvider tokenProvider;

    private static final String BLACKLIST_KEY = "token:blacklist:";

    public void addToBlacklist(String token) {
        long expiration = tokenProvider.getExpirationFromToken(token);
        redisTemplate.opsForValue().set(
                BLACKLIST_KEY + token, 
                "1", 
                expiration, 
                TimeUnit.MILLISECONDS
        );
    }

    public boolean isBlacklisted(String token) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(BLACKLIST_KEY + token));
    }
}

八、性能优化

8.1 批量操作

java 复制代码
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
    
    @Insert({
        "<script>",
        "INSERT INTO sys_user_role (user_id, role_id) VALUES ",
        "<foreach collection='list' item='item' separator=','>",
        "(#{item.userId}, #{item.roleId})",
        "</foreach>",
        "</script>"
    })
    int insertBatch(@Param("list") List<SysUserRole> list);
}

8.2 权限预加载

java 复制代码
@Service
public class PermissionPreloadService {

    @PostConstruct
    public void preloadPermissions() {
        // 系统启动时预加载常用权限数据到Redis
        List<SysMenu> allMenus = menuMapper.selectList(null);
        redisTemplate.opsForValue().set("all:permissions", allMenus);
    }
}

九、监控与日志

9.1 操作日志

java 复制代码
@Aspect
@Component
public class OperationLogAspect {

    @Around("@annotation(operationLog)")
    public Object recordLog(ProceedingJoinPoint joinPoint, 
                           OperationLog operationLog) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        Object result = joinPoint.proceed();
        
        long duration = System.currentTimeMillis() - startTime;
        
        // 记录操作日志
        OperationLogEntity log = new OperationLogEntity();
        log.setModule(operationLog.module());
        log.setOperation(joinPoint.getSignature().getName());
        log.setDuration(duration);
        log.setOperator(SecurityUtils.getCurrentUsername());
        log.setCreateTime(new Date());
        
        // 异步保存日志
        logService.saveLogAsync(log);
        
        return result;
    }
}

9.2 登录日志

java 复制代码
@Service
public class LoginLogService {

    public void recordLoginLog(String username, String ip, boolean success, String message) {
        LoginLog log = new LoginLog();
        log.setUsername(username);
        log.setIp(ip);
        log.setSuccess(success);
        log.setMessage(message);
        log.setLoginTime(new Date());
        
        loginLogMapper.insert(log);
    }
}

十、总结

本文详细介绍了从0到1搭建企业级权限管理系统的完整方案,涵盖了:

  1. 架构设计:清晰的分层架构和技术选型
  2. RBAC模型:经典的角色权限模型设计
  3. 数据库设计:完整的表结构和关系
  4. 核心实现:JWT认证、Spring Security集成、权限拦截
  5. 安全策略:密码加密、防重放攻击、Token管理
  6. 缓存优化:权限缓存、Token黑名单
  7. 性能优化:批量操作、权限预加载
  8. 监控日志:操作日志、登录日志

关键要点

  • JWT是无状态认证的理想选择,但需要考虑Token刷新和黑名单机制
  • RBAC模型简单有效,适合大多数企业场景
  • 权限缓存可以显著提升系统性能
  • 安全防护是多层次的,需要从认证、授权、防攻击等多个维度考虑
  • 日志和监控是系统稳定运行的保障

这套方案可以作为一个起点,根据实际业务需求进行扩展和优化,如增加数据权限、支持多租户、集成OAuth2等。

🎁 福利时间

如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。

知识库地址:https://farerboy.com/


相关推荐
逍遥德1 小时前
AI时代,计算机专业大学生学习指南
java·javascript·人工智能·学习·ai编程
ray_liang2 小时前
吐血整理JSON-RPC2.0的原理与应用
后端
蝎子莱莱爱打怪2 小时前
Claude Code 省 Token 小妙招:RTK + Caveman 组合拳
前端·人工智能·后端
Soofjan2 小时前
Redis(3):RDB 与 AOF、BGSAVE 与写时复制
后端
Maiko Star2 小时前
让 AI 开口说话:Spring AI Alibaba 语音合成(TTS)实战
java·人工智能·spring·springai
码事漫谈2 小时前
我的第一次移动端 AI 办公:在地铁上把 Bug 修了
后端
programhelp_2 小时前
Pinterest OA 题库大公开|Programhelp 独家整理(最新高频)
java·开发语言
少年白马醉春风丶2 小时前
从零构建 AIGC 无限画布:AIGCCanvasFlow 技术全解析
前端·后端·aigc