
引言:为什么权限管理是企业系统的基石
在任何企业级应用中,权限管理都是不可或缺的核心模块。一个完善的权限管理系统不仅要解决"谁能访问什么"的问题,还要兼顾安全性、可扩展性和易用性。
本文将带你从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的优势
- 简化管理:通过角色间接授权,避免直接给用户授权的管理复杂度
- 灵活扩展:新增权限只需修改角色权限关联,不影响用户
- 职责清晰:角色对应岗位职责,便于理解和维护
- 支持继承:可实现角色继承,满足复杂组织需求
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搭建企业级权限管理系统的完整方案,涵盖了:
- 架构设计:清晰的分层架构和技术选型
- RBAC模型:经典的角色权限模型设计
- 数据库设计:完整的表结构和关系
- 核心实现:JWT认证、Spring Security集成、权限拦截
- 安全策略:密码加密、防重放攻击、Token管理
- 缓存优化:权限缓存、Token黑名单
- 性能优化:批量操作、权限预加载
- 监控日志:操作日志、登录日志
关键要点:
- JWT是无状态认证的理想选择,但需要考虑Token刷新和黑名单机制
- RBAC模型简单有效,适合大多数企业场景
- 权限缓存可以显著提升系统性能
- 安全防护是多层次的,需要从认证、授权、防攻击等多个维度考虑
- 日志和监控是系统稳定运行的保障
这套方案可以作为一个起点,根据实际业务需求进行扩展和优化,如增加数据权限、支持多租户、集成OAuth2等。
🎁 福利时间
如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。
知识库地址:https://farerboy.com/
