当前位置: 首页 > news >正文

别再只写CRUD了!用SpringBoot+Vue给这个Demo加上JWT登录和权限管理

从基础Demo到企业级应用:SpringBoot+Vue实现JWT认证与动态权限控制

很多开发者完成前后端分离的基础Demo后,常常陷入"接下来该做什么"的迷茫。本文将带你从简单的任务管理系统出发,逐步引入企业级应用中不可或缺的JWT认证和RBAC权限控制,让你的项目真正具备生产环境可用性。

1. 为什么需要JWT和权限管理

在真实的企业应用中,单纯的数据展示远远不够。想象一下,你的任务管理系统如果任何人都能随意查看、修改所有任务,那将是一场灾难。我们需要解决三个核心问题:

  • 身份认证:确认用户是谁
  • 权限控制:确定用户能做什么
  • 安全传输:确保通信过程不被篡改

JWT(JSON Web Token)作为一种轻量级的认证方案,相比传统的Session有以下优势:

特性JWTSession
存储位置客户端服务端
扩展性适合分布式系统需要共享Session存储
跨域支持天然支持需要额外配置
性能无状态,减少服务端压力需要查询Session存储

2. 后端实现:SpringBoot整合JWT

2.1 引入JWT依赖

首先在pom.xml中添加JJWT依赖:

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

2.2 JWT工具类实现

创建JWT工具类处理token的生成和验证:

public class JwtUtil { private static final String SECRET_KEY = "your-256-bit-secret"; private static final long EXPIRATION_TIME = 864_000_000; // 10天 public static String generateToken(String username, List<String> roles) { return Jwts.builder() .setSubject(username) .claim("roles", roles) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }

2.3 用户认证服务

实现Spring Security的UserDetailsService:

@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapper.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), getAuthorities(user.getRoles()) ); } private Collection<? extends GrantedAuthority> getAuthorities(List<Role> roles) { return roles.stream() .map(role -> new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toList()); } }

3. 前端实现:Vue整合JWT认证

3.1 登录组件实现

创建登录页面处理用户认证:

<template> <div class="login-container"> <el-form :model="loginForm" :rules="rules" ref="loginForm"> <el-form-item prop="username"> <el-input v-model="loginForm.username" placeholder="用户名"></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" v-model="loginForm.password" placeholder="密码"></el-input> </el-form-item> <el-button type="primary" @click="handleLogin">登录</el-button> </el-form> </div> </template> <script> export default { data() { return { loginForm: { username: '', password: '' }, rules: { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], password: [{ required: true, message: '请输入密码', trigger: 'blur' }] } } }, methods: { handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { this.$axios.post('/auth/login', this.loginForm) .then(response => { localStorage.setItem('token', response.data.token) this.$router.push('/') }) } }) } } } </script>

3.2 请求拦截器配置

在main.js中添加请求拦截器,自动携带token:

axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => { return Promise.reject(error) }) axios.interceptors.response.use(response => { return response }, error => { if (error.response.status === 401) { router.push('/login') } return Promise.reject(error) })

4. 动态权限控制实现

4.1 后端权限控制

使用Spring Security配置基于角色的访问控制:

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/auth/**").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }

4.2 前端动态菜单

根据用户角色动态生成菜单:

<template> <el-menu :default-active="activeIndex" mode="horizontal"> <template v-for="item in menuItems"> <el-submenu v-if="item.children" :index="item.path" :key="item.path"> <template slot="title">{{ item.title }}</template> <el-menu-item v-for="child in item.children" :key="child.path" :index="child.path" @click="navigateTo(child.path)" > {{ child.title }} </el-menu-item> </el-submenu> <el-menu-item v-else :index="item.path" :key="item.path" @click="navigateTo(item.path)"> {{ item.title }} </el-menu-item> </template> </el-menu> </template> <script> export default { data() { return { activeIndex: '/', menuItems: [] } }, created() { this.fetchMenuItems() }, methods: { fetchMenuItems() { const token = localStorage.getItem('token') if (token) { const payload = JSON.parse(atob(token.split('.')[1])) const roles = payload.roles let items = [] if (roles.includes('ADMIN')) { items = [ { path: '/dashboard', title: '仪表盘' }, { path: '/management', title: '管理', children: [ { path: '/users', title: '用户管理' }, { path: '/tasks', title: '任务管理' } ] } ] } else if (roles.includes('USER')) { items = [ { path: '/dashboard', title: '仪表盘' }, { path: '/tasks', title: '我的任务' } ] } this.menuItems = items } }, navigateTo(path) { this.$router.push(path) } } } </script>

5. 项目优化与安全实践

5.1 Token刷新机制

实现无感知的token刷新:

public class JwtRefreshFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = resolveToken(request); if (token != null && JwtUtil.canTokenBeRefreshed(token)) { String refreshedToken = JwtUtil.refreshToken(token); response.setHeader("Authorization", "Bearer " + refreshedToken); } filterChain.doFilter(request, response); } private String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }

5.2 敏感操作日志记录

使用Spring AOP记录关键操作:

@Aspect @Component public class OperationLogAspect { @Autowired private OperationLogService logService; @Pointcut("@annotation(com.example.demo.annotation.OperationLog)") public void operationLogPointCut() {} @AfterReturning(pointcut = "operationLogPointCut()", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); OperationLog annotation = method.getAnnotation(OperationLog.class); String username = SecurityContextHolder.getContext().getAuthentication().getName(); String operation = annotation.value(); OperationLogEntity log = new OperationLogEntity(); log.setUsername(username); log.setOperation(operation); log.setMethod(method.getName()); log.setParams(JsonUtil.toJson(joinPoint.getArgs())); log.setResult(JsonUtil.toJson(result)); log.setCreateTime(new Date()); logService.save(log); } }

在实际项目中,我发现JWT的密钥管理尤为重要。曾经因为将密钥硬编码在代码中导致安全漏洞,后来改用环境变量配合配置中心动态获取,大大提升了安全性。另外,前端存储token时建议使用HttpOnly的Cookie,能有效防范XSS攻击。

http://www.jsqmd.com/news/844757/

相关文章:

  • 172 号卡分销代理须知|官方唯一邀请码 00500 及权益保障公告
  • B站缓存视频转换终极指南:5秒无损将m4s转为MP4的完整教程
  • 2026年四轴机械臂五大品牌深度对比评测与选购建议 - 品牌种草官
  • TPFanCtrl2:ThinkPad智能风扇控制终极指南,彻底解决过热与噪音问题
  • AMD Ryzen终极调试指南:3步解锁处理器隐藏性能的完整教程
  • 2026 疆内出行用车甄选:旅游自驾・商务接待・企业通勤・团体包车一站式租车服务企业实用选购指南 - 海棠依旧大
  • 终极Windows桌面整理指南:用NoFences告别混乱,免费实现高效分区管理
  • 2026年推荐言笔AI:高效去AI痕迹,轻松应对繁重编辑任务 - 降AI实验室
  • 2026石家庄自动化PLC编程培训优质机构推荐榜 - 元点智创
  • STM32H7实战:告别Bootloader,用QSPI Flash和内部Flash混合运行程序(含MDK配置避坑)
  • 从OBD到功能安全:聊聊Autosar Dem模块里故障数据的‘生老病死’与内存管理策略
  • 别再乱按了!示波器Autoset和Run/Stop的正确用法,看完这篇就够了
  • 用AG9311芯片DIY一个全能Type-C扩展坞:从原理图到PCB布局的保姆级教程
  • 民政部四级行政地址联动
  • 5分钟搞定B站视频下载:解锁大会员4K高清的完整教程
  • OpenHuman
  • 如何快速获取网易云和QQ音乐的精准LRC歌词?这款免费工具帮你一键搞定!
  • 【电脑自动化助手】 OpenClaw 一键部署教程(包含安装包)
  • VSCode搭建ROS开发环境:从环境配置到高效调试全攻略
  • 安装CentOS系统
  • 现货库存量大的HC-276合金厂商推荐:HC-276合金厂商联系方式 - 品牌2025
  • 深圳美国物流哪家靠谱? - 恒盛通物流
  • 百度网盘API离线下载终极指南:3步实现磁力链接一键转存
  • 数学函数双曲线音频图表(y=1/x 双曲线)|图表代码示例
  • LizzieYzy:围棋AI分析工具的三大突破,让你拥有职业棋手的复盘能力
  • 对比直接使用官方API通过聚合平台管理网站AI调用的体验
  • 推荐几家17-4Ph圆钢国内厂商:规格齐全的17-4Ph不锈钢厂商推荐 - 品牌2025
  • 基于二维码技术的设备管理系统实战 - 从架构设计到扫码盘点落地
  • MATLAB 基于多层编码遗传算法的车间调度优化
  • 紧急预警!Springer Nature最新政策生效:Perplexity生成综述需满足3层溯源验证(附自动化校验Python工具链)