如何用 Spring Security 构建无状态权限控制系统(含角色菜单控制)

本文是「EduCore 教务系统实战系列」第 3 篇,讲解我如何基于 Spring Security + JWT 搭建一套可拓展、无状态的权限控制系统,结合角色动态菜单,实现中后台常见的"前后端权限联动"。


🎯 权限控制目标

在 EduCore 教务系统中,我设定了三类核心角色:

  • 🧑‍💼 管理员(ADMIN):系统管理 + 全模块管理权限
  • 👨‍🏫 教师(TEACHER):可管理课程、学生、成绩
  • 🧑‍🎓 学生(STUDENT):仅可查看与自身有关的信息

目标是在后端使用 Spring Security 控制接口权限,在前端根据角色渲染对应菜单与页面,实现**"接口 + 页面"双重权限控制**。


🔐 为什么采用 Spring Security + JWT?

功能需求 技术方案
无状态、Token 认证 JWT
权限路径拦截、角色判断 Spring Security
登录角色动态控制菜单 前端根据 Token 中角色生成菜单树
扫码等场景适配 JWT 无需绑定 Session,移动端也可复用

🧱 系统权限架构图

plaintext 复制代码
[浏览器请求] ---> [JWT 鉴权过滤器] ---> [Spring Security 权限校验] ---> [控制器方法]
        |                                               |
  Token 中包含角色                            控制器标注角色权限 (@PreAuthorize)

✅ Spring Security 配置核心代码

🔧 1. 配置类(SecurityConfig)

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf().disable()
            .cors()
            .and()
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login", "/register", "/qr/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/teacher/**").hasRole("TEACHER")
                .requestMatchers("/student/**").hasRole("STUDENT")
                .anyRequest().authenticated()
            )
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }
}

🔒 2. 自定义 JWT 鉴权过滤器

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

    @Autowired private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {

        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            token = token.replace("Bearer ", "");
            Claims claims = jwtUtils.parseToken(token);
            String userId = claims.getSubject();
            String role = (String) claims.get("role");

            List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_" + role);
            UsernamePasswordAuthenticationToken authToken =
                new UsernamePasswordAuthenticationToken(userId, null, authorities);
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }

        chain.doFilter(request, response);
    }
}

🗂️ 接口权限控制结构(后端)

接口路径前缀 允许角色
/admin/** ADMIN
/teacher/** TEACHER
/student/** STUDENT

也可使用注解方式控制方法级权限:

java 复制代码
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/account/list")
public R listAllUsers() { ... }

🧭 前端如何控制菜单权限?

基于后端登录返回的角色信息,前端动态渲染路由与菜单。

步骤如下:

  1. 后端登录接口返回用户的 role
  2. Vue3 中使用 Pinia 存储当前用户角色
  3. 定义菜单数据结构(不同角色不同菜单)
  4. 登录后根据角色注入对应路由表

示例:Vue3 中动态菜单注入

ts 复制代码
const role = localStorage.getItem("role");

const routesMap = {
  ADMIN: [...adminRoutes],
  TEACHER: [...teacherRoutes],
  STUDENT: [...studentRoutes]
};

const router = useRouter();
routesMap[role].forEach(r => router.addRoute("ManagerLayout", r));

这样用户登录后才会注入该角色的页面路由,实现前端页面级权限隔离


🧠 常见问题解答

❓ 为什么要同时做前后端权限控制?

  • 后端控制接口权限是安全基线(避免恶意伪造请求)
  • 前端控制菜单页面是用户体验保障(防止"跳转404")

❓ 为什么使用 ROLE_ 前缀?

Spring Security 默认角色名称需要以 ROLE_ 开头,匹配时会自动加上。

❓ 可否基于数据库动态控制权限?

目前项目以硬编码为主,但完全可以扩展为数据库角色-菜单表形式,加载用户角色菜单进行动态渲染。


📦 结构总览

plaintext 复制代码
├── config
│   └── SecurityConfig.java
├── filter
│   └── JwtAuthenticationFilter.java
├── utils
│   └── JwtUtils.java
├── controller
│   ├── AdminController.java
│   ├── TeacherController.java
│   └── StudentController.java
└── vue-pages
    └── menuConfig.js // 各角色菜单配置

✅ 效果演示截图

(建议贴登录后各角色菜单界面截图)


🔚 总结

通过 Spring Security + JWT 的配合,我构建了一套适配三角色、无状态、可拓展的权限控制系统:

  • ✅ 后端权限基于角色配置路径 + 方法注解
  • ✅ 前端权限通过动态菜单与路由控制
  • ✅ Token 中包含角色信息,可实现完整身份传递
  • ✅ 无状态架构为扫码登录、多端适配打下基础

这部分是整个 EduCore 系统的安全核心,未来也可以扩展为 RBAC 权限模型或接入权限中心。


📌 下一篇预告

📌《项目中如何用策略模式实现多角色登录解耦?(附实战代码)》

敬请期待!


🔗 项目源码


🙋‍♂️ 如果你觉得这篇文章有帮助:

  • 点个赞 👍
  • 收藏 ⭐
  • 评论 💬
  • 关注我 👇 获取后续实战文章更新
相关推荐
胚芽鞘68126 分钟前
关于java项目中maven的理解
java·数据库·maven
岁忧1 小时前
(LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
java·c++·算法·leetcode·面试·go
CJi0NG1 小时前
【自用】JavaSE--算法、正则表达式、异常
java
Nejosi_念旧2 小时前
解读 Go 中的 constraints包
后端·golang·go
风无雨2 小时前
GO 启动 简单服务
开发语言·后端·golang
Hellyc2 小时前
用户查询优惠券之缓存击穿
java·redis·缓存
小明的小名叫小明2 小时前
Go从入门到精通(19)-协程(goroutine)与通道(channel)
后端·golang
斯普信专业组2 小时前
Go语言包管理完全指南:从基础到最佳实践
开发语言·后端·golang
今天又在摸鱼2 小时前
Maven
java·maven
老马啸西风2 小时前
maven 发布到中央仓库常用脚本-02
java·maven