本文是「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() { ... }
🧭 前端如何控制菜单权限?
基于后端登录返回的角色信息,前端动态渲染路由与菜单。
步骤如下:
- 后端登录接口返回用户的
role
- Vue3 中使用 Pinia 存储当前用户角色
- 定义菜单数据结构(不同角色不同菜单)
- 登录后根据角色注入对应路由表
示例: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 权限模型或接入权限中心。
📌 下一篇预告
📌《项目中如何用策略模式实现多角色登录解耦?(附实战代码)》
敬请期待!
🔗 项目源码
- GitHub:gitee.com/codevibe/gr...
- 后端完整权限配置 + 策略代码已上传,欢迎 Star、Fork
🙋♂️ 如果你觉得这篇文章有帮助:
- 点个赞 👍
- 收藏 ⭐
- 评论 💬
- 关注我 👇 获取后续实战文章更新