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


📌 下一篇预告

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

敬请期待!


🔗 项目源码


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

  • 点个赞 👍
  • 收藏 ⭐
  • 评论 💬
  • 关注我 👇 获取后续实战文章更新
相关推荐
無限進步D14 小时前
Java 运行原理
java·开发语言·入门
難釋懷14 小时前
安装Canal
java
是苏浙14 小时前
JDK17新增特性
java·开发语言
不光头强14 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp17 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多17 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood17 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员17 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai