如何用 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 权限模型或接入权限中心。


📌 下一篇预告

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

敬请期待!


🔗 项目源码


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

  • 点个赞 👍
  • 收藏 ⭐
  • 评论 💬
  • 关注我 👇 获取后续实战文章更新
相关推荐
长风朗月碎梦1 分钟前
动态规划优势
后端
寒水馨3 分钟前
Java 24 新特性解析与代码示例
java·开发语言·新特性·jdk24·java24
海风极客9 分钟前
又开发了一个优雅的小工具
后端·github
奋进的孤狼24 分钟前
【Java】在一个前台界面中动态展示多个数据表的字段及数据
java·数据库·oracle
活椰拿铜40 分钟前
令牌桶限流算法的实现
后端·go
LEosOna42 分钟前
🚀 从零到一:百万级数据Excel导出技术方案实践
后端
Demonslzh42 分钟前
基于Langchain和DeepSeek的AI Agent记账系统:Fireflyiii-AI-Recorder
后端·agent·ai编程
葫芦和十三1 小时前
Go 泛型“黑话”:any 和 interface{} 完全一样吗?
后端·go·trae
CodeSaku1 小时前
是设计模式,我们有救了!!!(二、工厂模式)
后端
IT利刃出鞘1 小时前
Intellij Idea--解决Cannot download “https://start.spring.io‘: Connect timedout
java·ide·intellij-idea