
从基础Demo到企业级应用SpringBootVue实现JWT认证与动态权限控制很多开发者完成前后端分离的基础Demo后常常陷入接下来该做什么的迷茫。本文将带你从简单的任务管理系统出发逐步引入企业级应用中不可或缺的JWT认证和RBAC权限控制让你的项目真正具备生产环境可用性。1. 为什么需要JWT和权限管理在真实的企业应用中单纯的数据展示远远不够。想象一下你的任务管理系统如果任何人都能随意查看、修改所有任务那将是一场灾难。我们需要解决三个核心问题身份认证确认用户是谁权限控制确定用户能做什么安全传输确保通信过程不被篡改JWT(JSON Web Token)作为一种轻量级的认证方案相比传统的Session有以下优势特性JWTSession存储位置客户端服务端扩展性适合分布式系统需要共享Session存储跨域支持天然支持需要额外配置性能无状态减少服务端压力需要查询Session存储2. 后端实现SpringBoot整合JWT2.1 引入JWT依赖首先在pom.xml中添加JJWT依赖dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.2/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.2/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.2/version scoperuntime/scope /dependency2.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, ListString 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的UserDetailsServiceService 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(ListRole roles) { return roles.stream() .map(role - new SimpleGrantedAuthority(role.getName())) .collect(Collectors.toList()); } }3. 前端实现Vue整合JWT认证3.1 登录组件实现创建登录页面处理用户认证template div classlogin-container el-form :modelloginForm :rulesrules refloginForm el-form-item propusername el-input v-modelloginForm.username placeholder用户名/el-input /el-form-item el-form-item proppassword el-input typepassword v-modelloginForm.password placeholder密码/el-input /el-form-item el-button typeprimary clickhandleLogin登录/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(/) }) } }) } } } /script3.2 请求拦截器配置在main.js中添加请求拦截器自动携带tokenaxios.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-activeactiveIndex modehorizontal template v-foritem in menuItems el-submenu v-ifitem.children :indexitem.path :keyitem.path template slottitle{{ item.title }}/template el-menu-item v-forchild in item.children :keychild.path :indexchild.path clicknavigateTo(child.path) {{ child.title }} /el-menu-item /el-submenu el-menu-item v-else :indexitem.path :keyitem.path clicknavigateTo(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) } } } /script5. 项目优化与安全实践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攻击。