SpringSecurity5.x实战:从零配置JWT认证与RBAC权限控制(附完整代码)
SpringSecurity5.x实战从零配置JWT认证与RBAC权限控制附完整代码在企业级应用开发中安全始终是不可忽视的核心环节。想象一下这样的场景你的电商平台需要区分普通用户与管理员权限同时要求用户登录状态能够跨服务保持还要防范各种常见的安全威胁。这正是SpringSecurity框架大显身手的时刻——作为Spring生态中经过工业级验证的安全解决方案它能够以声明式的方式为你的系统构建多层次防护。本文将带你从零开始用最新SpringSecurity5.x版本实现包含JWT无状态认证和RBAC权限控制的完整方案。不同于基础教程我们重点关注生产环境中必须考虑的五个关键维度数据库用户存储方案、密码安全处理、精细化权限拦截、无状态会话管理以及异常处理机制。所有代码均经过真实项目验证你可以直接集成到自己的SpringBoot2.7项目中。1. 环境准备与基础配置1.1 初始化SpringBoot工程使用SpringInitializr创建项目时除了基础的Web依赖需要特别添加以下安全相关依赖dependencies !-- SpringSecurity核心库 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency !-- JWT支持库 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency !-- 数据库访问 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency /dependencies提示建议使用Java17配合SpringBoot3.x以获得最佳安全特性支持本文示例同样兼容SpringBoot2.7.x版本1.2 安全配置骨架搭建创建基础安全配置类时现代SpringSecurity推荐使用组件式配置而非传统的继承方式Configuration EnableWebSecurity RequiredArgsConstructor public class SecurityConfig { private final UserDetailsServiceImpl userDetailsService; Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() // 生产环境应配置CSRF保护 .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/**).permitAll() .anyRequest().authenticated() ) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } }关键配置解析csrf().disable()开发阶段暂时关闭CSRF保护生产环境需结合前端框架配置SessionCreationPolicy.STATELESS声明采用无状态会话为JWT集成做准备2. 数据库用户体系构建2.1 RBAC模型设计规范的RBAC模型通常需要五张核心表CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, enabled BOOLEAN DEFAULT true ); CREATE TABLE roles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) UNIQUE NOT NULL ); CREATE TABLE user_roles ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id), FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id) ); CREATE TABLE permissions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) UNIQUE NOT NULL ); CREATE TABLE role_permissions ( role_id BIGINT NOT NULL, permission_id BIGINT NOT NULL, PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES roles(id), FOREIGN KEY (permission_id) REFERENCES permissions(id) );2.2 密码安全处理永远不要存储明文密码BCrypt是目前最推荐的密码哈希算法Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // 强度因子建议10-12 }用户注册时的密码处理示例Transactional public User registerUser(UserRegistrationDto dto) { if(userRepository.existsByUsername(dto.getUsername())) { throw new UsernameExistsException(); } User user new User(); user.setUsername(dto.getUsername()); user.setPassword(passwordEncoder.encode(dto.getPassword())); Role userRole roleRepository.findByName(ROLE_USER) .orElseThrow(() - new RoleNotFoundException()); user.setRoles(Set.of(userRole)); return userRepository.save(user); }3. JWT认证完整实现3.1 JWT工具类封装Component RequiredArgsConstructor public class JwtTokenProvider { private final JwtProperties jwtProperties; public String generateToken(UserDetails userDetails) { MapString, Object claims new HashMap(); claims.put(roles, userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() jwtProperties.getExpiration())) .signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret()) .compact(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { throw new InvalidJwtException(Expired or invalid JWT token); } } public String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(jwtProperties.getSecret()) .parseClaimsJws(token) .getBody() .getSubject(); } }3.2 认证过滤器实现public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider; private final UserDetailsService userDetailsService; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt getJwtFromRequest(request); if (StringUtils.hasText(jwt) tokenProvider.validateToken(jwt)) { String username tokenProvider.getUsernameFromToken(jwt); UserDetails userDetails userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception ex) { logger.error(Could not set user authentication, ex); } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken request.getHeader(Authorization); if (StringUtils.hasText(bearerToken) bearerToken.startsWith(Bearer )) { return bearerToken.substring(7); } return null; } }4. 精细化权限控制实战4.1 方法级权限注解SpringSecurity提供了细粒度的权限控制注解RestController RequestMapping(/api/admin) PreAuthorize(hasRole(ADMIN)) public class AdminController { GetMapping(/users) PreAuthorize(hasAuthority(USER_READ)) public ListUser getAllUsers() { return userService.findAll(); } PostMapping(/users) PreAuthorize(hasAuthority(USER_WRITE)) public User createUser(Valid RequestBody UserCreateDto dto) { return userService.createUser(dto); } }4.2 动态权限决策对于更复杂的场景可以实现自定义权限决策器Component public class CustomPermissionEvaluator implements PermissionEvaluator { Override public boolean hasPermission(Authentication auth, Object target, Object permission) { if ((auth null) || (target null) || !(permission instanceof String)){ return false; } String targetType target.getClass().getSimpleName().toUpperCase(); return hasPrivilege(auth, targetType, permission.toString()); } private boolean hasPrivilege(Authentication auth, String target, String permission) { return auth.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .anyMatch(auth - auth.equals(permission _ target)); } }注册自定义决策器Configuration public class MethodSecurityConfig { Bean public MethodSecurityExpressionHandler methodSecurityExpressionHandler() { DefaultMethodSecurityExpressionHandler handler new DefaultMethodSecurityExpressionHandler(); handler.setPermissionEvaluator(new CustomPermissionEvaluator()); return handler; } }5. 生产级增强配置5.1 异常处理机制自定义安全异常处理器RestControllerAdvice public class SecurityExceptionHandler { ExceptionHandler(AccessDeniedException.class) public ResponseEntityErrorResponse handleAccessDenied(AccessDeniedException ex) { ErrorResponse response new ErrorResponse( FORBIDDEN, Insufficient permissions, HttpStatus.FORBIDDEN.value()); return new ResponseEntity(response, HttpStatus.FORBIDDEN); } ExceptionHandler(AuthenticationException.class) public ResponseEntityErrorResponse handleAuthentication(AuthenticationException ex) { ErrorResponse response new ErrorResponse( UNAUTHORIZED, Authentication failed, HttpStatus.UNAUTHORIZED.value()); return new ResponseEntity(response, HttpStatus.UNAUTHORIZED); } }5.2 响应式CORS配置现代前后端分离架构必须正确配置CORSBean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config new CorsConfiguration(); config.setAllowedOrigins(List.of(https://yourdomain.com)); config.setAllowedMethods(List.of(GET, POST, PUT, DELETE)); config.setAllowedHeaders(List.of(*)); config.setExposedHeaders(List.of(Authorization)); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return source; }5.3 安全头信息加固增强HTTP安全头配置Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .headers(headers - headers .contentSecurityPolicy(csp - csp .policyDirectives(default-src self)) .frameOptions().sameOrigin() .httpStrictTransportSecurity() .includeSubDomains(true) .maxAgeInSeconds(31536000) ); // ...其他配置 }6. 测试与调试技巧6.1 集成测试方案使用SpringSecurity测试支持SpringBootTest AutoConfigureMockMvc class SecurityIntegrationTest { Autowired private MockMvc mockMvc; Test WithMockUser(roles USER) void whenUserAccessUserEndpoint_thenSuccess() throws Exception { mockMvc.perform(get(/api/user/profile)) .andExpect(status().isOk()); } Test WithMockUser(roles USER) void whenUserAccessAdminEndpoint_thenForbidden() throws Exception { mockMvc.perform(get(/api/admin/users)) .andExpect(status().isForbidden()); } }6.2 实时调试建议开发阶段可以开启安全调试日志# application.properties logging.level.org.springframework.securityDEBUG logging.level.org.springframework.webTRACE对于JWT调试推荐使用jwt.io解码工具验证token内容。