本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:权限管理系统是保障信息安全与操作规范的核心机制,广泛应用于企业级IT系统中。本项目基于Spring Boot、MyBatis和Vue.js三大主流框架构建,涵盖用户管理、角色管理、资源分配、权限控制、审计日志及动态权限调整等核心功能。通过前后端分离架构,实现安全高效的权限控制体系。项目包含完整的设计文档、数据库脚本、源码与测试用例,适合开发者学习框架集成与企业级权限管理的实战应用。

权限管理系统:从零构建高安全、可扩展的企业级RBAC实战

你有没有遇到过这样的场景?半夜接到告警,发现某个普通员工居然偷偷访问了财务系统的敏感接口;或者新来的运维同事误删了生产数据库……这些问题背后,往往都指向同一个薄弱环节—— 权限管理不到位

在现代企业应用中,权限系统早已不是“有就行”的功能模块,而是关乎数据安全、合规审计和业务稳定运行的 核心基础设施 。一个设计良好的权限体系,不仅能防止越权操作,还能提升团队协作效率,让组织架构与系统权限天然对齐。

今天,我们就来手把手搭建一套完整的企业级权限管理系统,涵盖前后端全链路实现。整个过程不玩虚的,直接上硬核代码+真实工程实践,带你打通权限控制的最后一公里 🚀


想象一下这个画面:你在一家金融科技公司负责后台系统开发。某天产品经理找到你说:“我们需要给交易员、风控员、管理员分配不同的操作权限,比如交易员只能提交订单,审批通过必须由风控员复核。”

这听起来是不是很熟悉?没错,这就是典型的 RBAC(基于角色的访问控制)模型 的应用场景。而我们要做的,就是把这种复杂的权限逻辑,用技术手段精准落地。

但别急着写代码!先搞清楚一件事:很多人混淆了“认证”和“授权”,结果导致系统出现严重的安全漏洞。

👉 认证(Authentication) 是确认你是谁 —— 比如登录时输入用户名密码;
👉 授权(Authorization) 是决定你能做什么 —— 比如登录后是否允许删除用户。

简单说:
🔑 认证 = “你是张三吗?”
🔐 授权 = “张三能删数据库吗?”

搞反了顺序,轻则功能错乱,重则被黑客钻空子 😱

那市面上常见的权限模型有哪些呢?我们来看个演化图谱:

graph LR
    A[DAC - 自主访问控制] --> B[MAC - 强制访问控制]
    B --> C[ABAC - 属性基访问控制]
    C --> D[RBAC - 基于角色的访问控制]
  • DAC :文件所有者自己决定谁能读写,太随意,不适合企业环境;
  • MAC :操作系统级别强制策略,像军事系统那种“绝密/机密/公开”分级,太死板;
  • ABAC :根据用户属性(部门、职级)、资源属性、环境条件动态判断,灵活但复杂;
  • RBAC :折中方案,通过“角色”作为桥梁,既灵活又易于管理。

对于我们大多数业务系统来说, RBAC 是最佳选择 。它把“用户 ↔ 权限”的多对多关系,变成“用户 → 角色 → 权限”的链式结构,大大降低了维护成本。

举个例子:

金融系统里,“交易员”角色拥有【提交订单】权限,“风控员”角色拥有【复核订单】权限。当新人入职时,只需给他分配对应角色,无需手动勾选一堆权限;离职时一键解绑角色即可回收权限。

这就叫—— 职责分离 + 最小权限原则

好了,理论铺垫完毕,咱们进入实战环节!

Spring Boot后端服务搭建:安全优先的设计哲学

现在开始搭架子。我们用 Spring Boot 作为后端框架,原因很简单:生态成熟、社区强大、整合方便,特别是配合 Spring Security 几乎是Java领域的事实标准。

不过别一上来就莽代码!好的系统一定是“设计先行”。我们在项目初期就要确立几个关键原则:

分层清晰,各司其职 💼

推荐使用经典的四层架构:

层级 职责
Controller 处理HTTP请求,参数校验,返回统一响应
Service 封装核心业务逻辑,协调多个DAO操作
Mapper/Repository 数据访问层,执行SQL或JPA查询
Entity/DTO 领域对象与传输对象

每一层只跟相邻层打交道,形成清晰的调用链条。这样做的好处是:后期改需求、加日志、做单元测试都特别方便。

来看一段真实的控制器代码:

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

    @Autowired
    private AuthService authService;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        String token = authService.authenticate(request.getUsername(), request.getPassword());
        return ResponseEntity.ok(Map.of("token", token));
    }
}

注意看,这里 AuthController 只做了三件事:
1. 接收 /api/auth/login 的 POST 请求;
2. 把账号密码转发给 AuthService
3. 返回一个带JWT令牌的JSON。

真正的认证逻辑藏在 authService.authenticate() 里面,外面根本看不到细节。这就是所谓的“瘦控制器”原则—— Controller 不要写太多逻辑,否则会变得难以测试和维护

整个调用流程可以用一张图表示:

graph TD
    A[客户端] --> B[AuthController]
    B --> C[AuthService]
    C --> D[UserDetailsService]
    C --> E[JwtUtil]
    D --> F[UserMapper]
    F --> G[(MySQL)]

每一层像积木一样堆上去,松耦合、易替换。比如将来想换Redis存储用户信息?只需要改 UserMapper 实现类,上层完全不用动!

高内聚低耦合,模块化设计 🧩

为了进一步提升可维护性,我们还要遵守SOLID原则中的两条黄金法则:

  • 单一职责 :每个类只干一件事;
  • 依赖倒置 :面向接口编程,而不是具体实现。

拿角色权限服务举例:

// 定义接口
public interface RoleService {
    List<Role> getAllRoles();
    void assignPermissionsToRole(Long roleId, List<Long> permissionIds);
}

// 具体实现
@Service
@Transactional
public class RoleServiceImpl implements RoleService {

    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List<Role> getAllRoles() {
        return roleMapper.selectAll();
    }

    @Override
    @CacheEvict(value = "permissions", key = "#roleId")
    public void assignPermissionsToRole(Long roleId, List<Long> permissionIds) {
        roleMapper.deleteRolePermissions(roleId);
        if (permissionIds != null && !permissionIds.isEmpty()) {
            roleMapper.insertRolePermissions(roleId, permissionIds);
        }
    }
}

亮点在哪?

  • @Service 让Spring自动托管这个Bean;
  • @Transactional 保证“删旧权限 + 插新权限”是一个原子操作,失败自动回滚;
  • @CacheEvict 在修改权限后清除Redis缓存,避免脏数据;
  • 所有依赖通过 @Autowired 注入,没有 new XXX() 这种硬编码。

未来如果要支持远程调用(比如Feign),只要新增一个 RemoteRoleService 实现类,注入时切换实现即可,调用方无感知。

这才是真正意义上的 可扩展架构

安全是底线,必须前置考虑 🔐

权限系统本身就是安全设施,如果它自己都不安全,那岂不是笑话?

所以在设计阶段就得堵住常见漏洞:

✅ 输入验证不能少

所有外部传参都要校验,建议用 @Valid + Hibernate Validator:

@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest req) {
    // ...
}

并在 DTO 中加上约束注解:

public class RegisterRequest {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20, message = "用户名长度应在4-20之间")
    private String username;

    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$", 
             message = "密码需包含大小写字母和数字,至少8位")
    private String password;
}
✅ 敏感字段绝不暴露

数据库里的密码是加密存的,但在内存中也不能随便传递明文啊!

定义专门的传输对象(DTO),过滤掉敏感字段:

public class UserDto {
    private Long id;
    private String username;
    private String email;
    private List<String> roles; // 不包含password!

    public static UserDto from(User user, List<String> roles) {
        UserDto dto = new UserDto();
        dto.setId(user.getId());
        dto.setUsername(user.getUsername());
        dto.setEmail(user.getEmail());
        dto.setRoles(roles);
        return dto;
    }
}

你看,连getter/setter都没给password留位置,彻底杜绝泄露可能。

✅ 统一异常处理,别把堆栈甩给前端

曾经有个项目因为没处理好异常,导致攻击者通过错误信息猜出了表结构……血的教训!

@ControllerAdvice 全局拦截异常:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(Exception e) {
        return ResponseEntity.badRequest().body(
            new ErrorResponse("VALIDATION_ERROR", "参数校验失败")
        );
    }

    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<ErrorResponse> handleAuth(Exception e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(
            new ErrorResponse("ACCESS_DENIED", "无权访问该资源")
        );
    }
}

这样一来,不管哪里出错,返回格式都是统一的 {code: "...", message: "..."} ,前端处理起来也省心。


讲完设计思想,下一步就是初始化项目了。推荐使用 https://start.spring.io 这个官方脚手架工具,选好依赖一键生成工程模板。

我们需要的核心依赖包括:

  • Spring Web
  • Spring Security
  • MyBatis Framework
  • MySQL Driver
  • Lombok
  • JWT 支持(手动添加)

生成后的目录结构长这样:

src/
├── main/
│   ├── java/
│   │   └── com/example/permissionsystem/
│   │       ├── PermissionSystemApplication.java
│   │       ├── controller/
│   │       ├── service/
│   │       ├── mapper/
│   │       └── config/
│   └── resources/
│       ├── application.yml
│       ├── mybatis-config.xml
│       └── mapper/*.xml

主启动类加上 @SpringBootApplication ,Spring Boot就会自动扫描包、加载配置、装配Bean,开箱即用 ⚡️

接下来重点来了:如何集成 JWT无状态认证

传统Session需要服务器存状态,横向扩展困难;而JWT把用户信息编码进Token本身,服务端只负责签发和验证,非常适合微服务架构。

先引入依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

然后写个工具类生成和解析Token:

@Component
public class JwtUtil {
    private final String SECRET_KEY = "your-secret-key-change-in-production"; // 生产环境务必更换!
    private final long EXPIRATION_TIME = 864_000_000; // 10天

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject();
    }

    private boolean isTokenExpired(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration().before(new Date());
    }
}

解释一下关键点:
- generateToken 创建一个包含用户名、签发时间、过期时间和签名的JWT;
- validateToken 检查Token是否属于当前用户且未过期;
- getUsernameFromToken 解析Token获取用户名,用于后续权限比对。

这个工具会在登录成功后调用,返回给前端一个形如 eyJhbGciOiJIUzUxMiJ9.xxxxx 的字符串,后续每次请求都放在 Authorization: Bearer <token> 头里传回来。

最后别忘了配置多环境支持!开发、测试、生产应该用不同的数据库和安全策略。

spring:
  profiles:
    active: dev

---
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:mysql://localhost:3306/perm_dev?useSSL=false&serverTimezone=UTC
    username: root
    password: root

---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASS}
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: ${GITHUB_CLIENT_ID}
            clientSecret: ${GITHUB_CLIENT_SECRET}

通过 spring.profiles.active=prod 切换环境,生产环境的所有敏感配置都从环境变量注入,避免代码泄露风险。

flowchart LR
    A[开发者本地] -->|dev配置| B(Spring Boot App)
    C[测试服务器] -->|staging配置| B
    D[生产集群] -->|prod配置 + 环境变量| B

一套代码,多套部署,DevOps效率直接起飞 🚀


数据持久层设计:MyBatis搞定复杂权限查询

有了后端骨架,接下来得把数据模型定下来。毕竟权限系统本质上是在管理“谁有什么权限”的关系网。

我们继续采用 RBAC 模型 ,核心实体就四个: 用户、角色、权限、资源 。它们之间的关系如下图所示:

erDiagram
    sys_user ||--o{ sys_user_role : "has"
    sys_role ||--o{ sys_user_role : "assigned_to"
    sys_role ||--o{ sys_role_permission : "contains"
    sys_permission ||--o{ sys_role_permission : "granted_to"
    sys_user {
        bigint id PK
        varchar username
        varchar password
        varchar nickname
        varchar email
        tinyint status
        datetime create_time
        datetime update_time
    }
    sys_role {
        bigint id PK
        varchar role_code
        varchar role_name
        varchar description
        tinyint status
        datetime create_time
        datetime update_time
    }
    sys_permission {
        bigint id PK
        varchar perm_code
        varchar perm_name
        varchar resource_type
        varchar url_path
        varchar method
        bigint parent_id
        int sort_order
        tinyint status
        datetime create_time
        datetime update_time
    }
    sys_user_role {
        bigint user_id FK
        bigint role_id FK
    }
    sys_role_permission {
        bigint role_id FK
        bigint permission_id FK
    }

看到了吗?用户和角色是多对多,角色和权限也是多对多,中间靠两张关联表连接。这种设计最大的好处是—— 权限变更不影响用户表结构 ,扩展性强!

再来看看权限表的具体字段该怎么设计。我们要支持菜单、按钮、API三种资源类型,所以得有个通用的 sys_permission 表:

字段名 类型 说明
id BIGINT 主键
perm_code VARCHAR(64) 权限唯一编码,如 user:add
perm_name VARCHAR(50) 显示名称
resource_type ENUM(‘menu’,’button’,’api’) 资源类型
url_path VARCHAR(255) 对应路径
method VARCHAR(10) HTTP方法(仅API)
parent_id BIGINT 父节点ID,用于树形结构
sort_order INT 排序权重

举个实际例子:

- 用户管理(菜单)
  |- 用户列表(菜单)
     |- 查询用户(按钮)
     |- 新增用户(按钮)
     |- 删除用户(API POST /api/users/delete)

对应的数据库记录:

id perm_code perm_name resource_type url_path method parent_id
1 menu:user 用户管理 menu /user NULL NULL
2 menu:user:list 用户列表 menu /user/list NULL 1
3 btn:user:query 查询用户 button NULL NULL 2
4 btn:user:add 新增用户 button NULL NULL 2
5 api:user:delete 删除用户 api /api/users/delete POST 2

前端可以根据 perm_code 控制按钮显隐,后端可以用 AOP 拦截 @RequiresPermission("api:user:delete") 方法调用,做到 前后端权限双保险

中间表的设计也有讲究。比如 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) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
    INDEX idx_role_id (role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关键点:
- 使用 (user_id, role_id) 作为联合主键,节省空间;
- 添加外键约束,确保引用完整性;
- 单独为 role_id 建索引,方便查“某个角色下有哪些用户”。

高并发下还要注意事务一致性。比如批量分配角色的操作:

@Service
@Transactional
public class UserRoleService {

    @Autowired
    private UserRoleMapper userRoleMapper;

    public void assignRolesToUser(Long userId, List<Long> roleIds) {
        userRoleMapper.deleteByUserId(userId); // 清空旧权限
        if (!CollectionUtils.isEmpty(roleIds)) {
            userRoleMapper.batchInsert(userId, roleIds); // 批量插入
        }
    }
}

配合 XML 中的 <foreach> 实现高效批量插入:

<insert id="batchInsert">
    INSERT INTO sys_user_role (user_id, role_id)
    VALUES
    <foreach collection="roleIds" item="roleId" separator=",">
        (#{userId}, #{roleId})
    </foreach>
</insert>

这一招能让性能提升几十倍,尤其适合一次性导入上百个用户的场景。

至于 ORM 框架,我们选择 MyBatis-Plus ,因为它不仅保留了原生 MyBatis 对 SQL 的完全掌控力,还提供了大量便捷功能:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

配置完 application.yml 后,写个实体类:

@Data
@TableName("sys_user")
public class SysUser {
    private Long id;
    private String username;
    private String password;
    private LocalDateTime createTime;
}

Mapper 接口继承 BaseMapper<SysUser> 就自动拥有了 selectById , insert , updateById 等常用方法:

public interface UserMapper extends BaseMapper<SysUser> {
}

复杂查询怎么办?比如要一次性查出用户+角色+权限信息,就得写 JOIN SQL 并配置 ResultMap

<resultMap id="UserWithRolesAndPermissions" type="UserDetailDTO">
    <id property="userId" column="id"/>
    <result property="username" column="username"/>
    <collection property="roles" ofType="string">
        <result column="role_name"/>
    </collection>
    <collection property="permissions" ofType="string">
        <result column="perm_code"/>
    </collection>
</resultMap>

<select id="getUserDetailById" resultMap="UserWithRolesAndPermissions">
    SELECT u.*, r.role_name, p.perm_code
    FROM sys_user u
    LEFT JOIN sys_user_role ur ON u.id = ur.user_id
    LEFT JOIN sys_role r ON ur.role_id = r.id
    LEFT JOIN sys_role_permission rp ON r.id = rp.role_id
    LEFT JOIN sys_permission p ON rp.permission_id = p.id
    WHERE u.id = #{userId}
</select>

这样一次查询就能拿到完整的权限视图,避免N+1问题,前端渲染菜单快如闪电 ⚡️

还有更酷的——动态SQL!比如按用户名、状态、创建时间组合筛选用户:

<select id="listUsers" parameterType="QueryUserReq" resultType="SysUser">
    SELECT * FROM sys_user
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="startTime != null">
            AND create_time >= #{startTime}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

<where> 标签会智能处理AND/OR开头的问题, <if> 实现条件拼接,再也不用手动拼字符串了,防SQL注入杠杠的!


Vue.js前端权限控制:让UI随权限而变

后端稳了,前端也不能掉链子。很多团队只做了接口权限,却忽略了页面元素的控制,结果出现了“按钮能点但接口报403”的尴尬局面。

我们的目标是: 用户看到的每一个按钮、每一条菜单,都是他真正有权操作的

为此,我们基于 Vue 3 + TypeScript + Pinia 搭建前端工程:

vue create permission-frontend
# 选择 Vue 3, TypeScript, Router, Pinia, Sass

目录结构清晰划分:

目录 用途
/src/views 页面组件
/src/components 通用UI组件
/src/router 路由配置
/src/store 状态管理
/src/directives 自定义指令
/src/utils 工具函数

动态路由:登录后才知能去哪 🗺️

传统静态路由所有人都能看到所有页面,存在安全隐患。我们必须改成 后端返回路由表 + 前端动态挂载 的模式。

假设后端 /api/user/routes 返回:

[
  {
    "path": "/dashboard",
    "name": "Dashboard",
    "component": "views/Dashboard.vue",
    "meta": { "title": "仪表盘", "roles": ["admin", "user"] }
  },
  {
    "path": "/user",
    "name": "UserManagement",
    "component": "views/User/List.vue",
    "meta": { "title": "用户管理", "roles": ["admin"] }
  }
]

前端收到后动态注册:

export const loadDynamicRoutes = async (router: any) => {
  const res = await fetch('/api/user/routes', {
    headers: { Authorization: `Bearer ${getToken()}` }
  });
  const routes: RouteRecordRaw[] = await res.json();

  routes.forEach(route => {
    router.addRoute({
      ...route,
      component: () => import(`@/${route.component}`)
    });
  });
};

利用 import() 实现代码分割,只有访问时才加载对应模块,首屏更快!

同时借助 meta 字段携带权限标识:

meta: {
  title: '系统设置',
  roles: ['admin'],
  permissions: ['system:config:edit']
}

这些元信息将在后续的守卫和菜单渲染中起决定作用。

流程图如下:

graph TD
    A[用户登录] --> B{已登录?}
    B -- 是 --> C[拉取专属路由表]
    C --> D[转换component为异步导入]
    D --> E[调用addRoute逐个注册]
    E --> F[完成路由初始化]
    F --> G[跳转首页]
    B -- 否 --> H[跳转登录页]

自定义指令:一行代码控制按钮显隐 ✨

细粒度权限控制最常用的手段就是自定义指令:

// directives/permission.ts
const permissionDirective: Directive = {
  mounted(el, binding) {
    const requiredRoles = binding.value;
    const hasPerm = usePermissionStore().hasRole(requiredRoles);

    if (!hasPerm) {
      el.parentNode?.removeChild(el); // 直接移除DOM,更安全
    }
  }
};

app.directive('permission', permissionDirective);

使用起来超级简单:

<button v-permission="['admin']">删除用户</button>

相比 v-if v-show ,这种方式连元素都不会出现在DOM树里,从根本上杜绝了手动启用的可能性。

对于需要提示的场景,还可以封装一个 <auth-button> 组件:

<auth-button :perms="['user:create']" type="primary">新增</auth-button>

点击无权限按钮时弹Toast提醒,用户体验更好。

路由守卫:最后一道防线 🔒

即使前端渲染了正确菜单,用户仍可能手动输入URL访问非法页面。因此必须设置全局前置守卫:

router.beforeEach(async (to, from, next) => {
  const whiteList = ['/login', '/403'];
  const token = getToken();

  if (!token && !whiteList.includes(to.path)) {
    return next('/login');
  }

  if (!usePermStore().isLoaded) {
    await usePermStore().fetchPermissions();
  }

  const requiredRoles = to.meta.roles;
  if (requiredRoles && !usePermStore().hasRole(requiredRoles)) {
    next('/403');
  } else {
    next();
  }
});

配合友好的403页面:

<h1>403 - 访问被拒绝</h1>
<p>您没有权限查看此页面。</p>
<el-button @click="$router.push('/')">返回首页</el-button>

真正做到层层设防,滴水不漏!


实战演练:全流程打通权限分配

现在让我们走一遍完整的权限分配流程。

用户管理:密码安全是第一课 🔐

新增用户时,密码必须加密存储:

@Autowired
private PasswordEncoder passwordEncoder; // 默认是 BCryptPasswordEncoder

user.setPassword(passwordEncoder.encode(rawPassword));

BCrypt的优势在于:
- 每次加密结果都不同(盐值内置);
- 抗彩虹表攻击;
- 可调节强度(默认10轮哈希)。

密码重置建议走邮箱链接方式,链接带过期Token,点击后跳转修改页面。

角色绑定:可视化勾选权限 🎯

前端用 el-tree 展示权限树:

<el-tree
  :data="permissionTree"
  show-checkbox
  node-key="id"
  ref="tree">
</el-tree>

<button @click="save()">保存角色权限</button>

<script>
async save() {
  const ids = this.$refs.tree.getCheckedKeys();
  await api.post(`/roles/${roleId}/perms`, { permIds: ids });
}
</script>

后端接收并更新中间表:

@PutMapping("/roles/{roleId}/perms")
public void updateRolePerms(@PathVariable Long roleId, @RequestBody Set<Long> permIds) {
    roleService.updatePermissions(roleId, permIds);
}

方法级权限:AOP自动拦截 ⚔️

定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
    String value(); // 如 "user:delete"
}

结合AOP进行校验:

@Aspect
@Component
public class PermissionAspect {

    @Around("@annotation(perm)")
    public Object check(ProceedingJoinPoint pjp, RequiresPermission perm) {
        String required = perm.value();
        Long userId = getCurrentUserId();

        if (!userHasPermission(userId, required)) {
            throw new AccessDeniedException("权限不足");
        }

        return pjp.proceed();
    }
}

使用时只需加个注解:

@RequiresPermission("user:delete")
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
    userService.delete(id);
}

干净利落,毫无侵入性!

审计日志:谁干了什么一清二楚 📜

每个敏感操作都要留痕:

@Log(action = "删除用户", module = "用户管理")
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) { ... }

AOP捕获并异步写入数据库:

@After("@annotation(log)")
public void record(JoinPoint jp, Log log) {
    AuditLog auditLog = buildLog(jp, log);
    logAsyncService.saveAsync(auditLog); // 异步落库,不影响主流程
}

日志表支持按时间、用户、模块检索,可用于安全审计和故障排查。


部署与热更新:不停机能改权限吗?🔄

当然可以!我们采用“Redis缓存 + 消息队列通知”的方案。

权限变更时:

@Autowired
private RedisTemplate<String, Set<String>> redisTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;

public void updatePermissions(Long userId) {
    // 更新Redis缓存
    Set<String> newPerms = loadUserPermissions(userId);
    redisTemplate.opsForValue().set("user:" + userId + ":perms", newPerms, Duration.ofHours(2));

    // 广播消息
    rabbitTemplate.convertAndSend("perm.exchange", "perm.updated", Map.of("userId", userId));
}

前端通过WebSocket监听事件:

const ws = new WebSocket('ws://localhost:8080/ws');
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'PERMISSION_UPDATE' && data.userId === myId) {
    location.reload(); // 或局部刷新菜单
  }
};

实现 不重启服务、权限实时生效 的效果!

最后用Docker容器化部署:

FROM openjdk:17-jdk-alpine
COPY target/auth-service.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

配合CI/CD流水线,从提交代码到上线全自动完成,效率翻倍!


整套系统跑通之后你会发现:权限管理不再是负担,而是一种 赋能机制 。它让组织规则数字化,让安全防护自动化,让团队协作更顺畅。

而这,正是现代企业应用应有的样子 💪

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:权限管理系统是保障信息安全与操作规范的核心机制,广泛应用于企业级IT系统中。本项目基于Spring Boot、MyBatis和Vue.js三大主流框架构建,涵盖用户管理、角色管理、资源分配、权限控制、审计日志及动态权限调整等核心功能。通过前后端分离架构,实现安全高效的权限控制体系。项目包含完整的设计文档、数据库脚本、源码与测试用例,适合开发者学习框架集成与企业级权限管理的实战应用。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐