新闻详情

新闻详情

首页 / 资讯中心 / 详情

SpringSecurity与JWT实现安全认证的最佳实践

发布时间:2026/9/12 12:36:06来源:尧图网络
SpringSecurity与JWT实现安全认证的最佳实践
1. 为什么需要SpringSecurity与JWT的权限认证组合在现代Web应用开发中认证(Authentication)和授权(Authorization)是两个核心的安全需求。SpringSecurity作为Spring生态中的安全框架提供了强大的认证和授权能力而JWT(JSON Web Token)则是一种轻量级的跨域认证解决方案。我曾在多个企业级项目中实现过这种组合方案发现它特别适合以下场景前后端分离架构下的用户认证微服务间的安全通信需要支持多终端(Web/App/小程序)的统一认证需要无状态会话管理的分布式系统提示SpringSecurity默认使用Session-based认证而JWT则是Token-based方案。两者结合时需要注意会话管理策略的调整。2. 基础环境搭建与依赖配置2.1 初始化SpringBoot项目首先创建一个基础的SpringBoot项目我推荐使用Spring Initializr生成项目骨架curl https://start.spring.io/starter.zip \ -d dependenciesweb,security \ -d javaVersion17 \ -d packagingjar \ -d artifactIdspring-security-jwt-demo \ -o demo.zip解压后我们需要添加JWT相关依赖。在pom.xml中添加dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency2.2 安全配置类创建基础的安全配置类这里我们先禁用CSRF保护API服务通常不需要Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/**).permitAll() .anyRequest().authenticated() ) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } }关键点说明SessionCreationPolicy.STATELESS设置为无状态因为我们使用JWT/api/auth/**开放给认证相关端点其他所有请求都需要认证3. JWT工具类实现3.1 JWT密钥管理我建议使用HMAC-SHA算法生成密钥这种方式简单且安全Component public class JwtTokenProvider { Value(${jwt.secret}) private String jwtSecret; Value(${jwt.expiration}) private long jwtExpirationInMs; public String generateToken(Authentication authentication) { UserPrincipal userPrincipal (UserPrincipal) authentication.getPrincipal(); Date now new Date(); Date expiryDate new Date(now.getTime() jwtExpirationInMs); return Jwts.builder() .setSubject(Long.toString(userPrincipal.getId())) .setIssuedAt(new Date()) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, jwtSecret) .compact(); } public Long getUserIdFromToken(String token) { Claims claims Jwts.parser() .setSigningKey(jwtSecret) .parseClaimsJws(token) .getBody(); return Long.parseLong(claims.getSubject()); } public boolean validateToken(String authToken) { try { Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); return true; } catch (SignatureException ex) { log.error(Invalid JWT signature); } catch (MalformedJwtException ex) { log.error(Invalid JWT token); } catch (ExpiredJwtException ex) { log.error(Expired JWT token); } catch (UnsupportedJwtException ex) { log.error(Unsupported JWT token); } catch (IllegalArgumentException ex) { log.error(JWT claims string is empty); } return false; } }3.2 自定义UserDetailsServiceSpringSecurity需要UserDetailsService来加载用户信息Service public class CustomUserDetailsService implements UserDetailsService { Autowired private UserRepository userRepository; Override Transactional public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException { User user userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail) .orElseThrow(() - new UsernameNotFoundException(User not found with username or email : usernameOrEmail) ); return UserPrincipal.create(user); } Transactional public UserDetails loadUserById(Long id) { User user userRepository.findById(id).orElseThrow( () - new ResourceNotFoundException(User, id, id) ); return UserPrincipal.create(user); } }4. 认证流程实现4.1 登录认证接口创建认证控制器处理登录请求RestController RequestMapping(/api/auth) public class AuthController { Autowired private AuthenticationManager authenticationManager; Autowired private JwtTokenProvider tokenProvider; PostMapping(/login) public ResponseEntity? authenticateUser(Valid RequestBody LoginRequest loginRequest) { Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsernameOrEmail(), loginRequest.getPassword() ) ); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt tokenProvider.generateToken(authentication); return ResponseEntity.ok(new JwtAuthenticationResponse(jwt)); } }4.2 JWT认证过滤器创建过滤器来解析请求头中的JWTpublic class JwtAuthenticationFilter extends OncePerRequestFilter { Autowired private JwtTokenProvider tokenProvider; Autowired private CustomUserDetailsService customUserDetailsService; 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)) { Long userId tokenProvider.getUserIdFromToken(jwt); UserDetails userDetails customUserDetailsService.loadUserById(userId); 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 in security context, 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; } }记得在SecurityConfig中注册这个过滤器Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // 其他配置... }5. 基于RBAC的权限控制5.1 角色与权限设计我推荐使用RBAC(基于角色的访问控制)模型。首先定义角色枚举public enum RoleName { ROLE_USER, ROLE_ADMIN }然后创建权限实体Entity Table(name permissions) public class Permission { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Enumerated(EnumType.STRING) Column(length 60) private PermissionName name; // getters and setters } public enum PermissionName { USER_READ, USER_WRITE, POST_READ, POST_WRITE }5.2 自定义权限注解创建自定义注解来简化权限控制Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface HasPermission { PermissionName[] value(); }然后创建对应的权限评估器Component public class PermissionEvaluatorImpl implements PermissionEvaluator { Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { if ((authentication null) || (permission null)) { return false; } String perm permission.toString().toUpperCase(); return authentication.getAuthorities().stream() .anyMatch(a - a.getAuthority().equals(perm)); } Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; // 本例中不实现 } }5.3 全局方法安全配置启用方法级安全控制Configuration EnableGlobalMethodSecurity( prePostEnabled true, securedEnabled true, jsr250Enabled true ) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { Override protected MethodSecurityExpressionHandler createExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler new DefaultMethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(new PermissionEvaluatorImpl()); return expressionHandler; } }现在可以在控制器方法上使用权限控制了GetMapping(/users) PreAuthorize(hasRole(ADMIN)) public ListUser getAllUsers() { return userRepository.findAll(); } PostMapping(/users) HasPermission({PermissionName.USER_WRITE}) public User createUser(Valid RequestBody User user) { return userRepository.save(user); }6. 常见问题与解决方案6.1 Token过期处理在实际项目中我遇到过Token过期导致用户体验差的问题。解决方案是实现Token刷新机制PostMapping(/refresh) public ResponseEntity? refreshToken(HttpServletRequest request) { String oldToken tokenProvider.getJwtFromRequest(request); if (!tokenProvider.validateToken(oldToken)) { throw new BadCredentialsException(Invalid token); } Long userId tokenProvider.getUserIdFromToken(oldToken); UserPrincipal userPrincipal (UserPrincipal) customUserDetailsService.loadUserById(userId); String newToken tokenProvider.generateToken( new UsernamePasswordAuthenticationToken( userPrincipal, null, userPrincipal.getAuthorities())); return ResponseEntity.ok(new JwtAuthenticationResponse(newToken)); }6.2 跨域问题处理前后端分离项目常遇到跨域问题我通常这样配置Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }然后在SecurityConfig中注册http.cors().and()...6.3 性能优化建议在高并发场景下JWT验证可能成为性能瓶颈。我的优化经验使用非对称加密算法(如RS256)替代对称加密实现JWT黑名单机制用于主动注销对频繁访问的接口添加缓存使用较短的Token有效期配合刷新机制7. 安全最佳实践7.1 敏感信息保护在实现过程中我总结了几点安全注意事项永远不要在JWT中存储敏感信息使用HTTPS传输Token设置合理的Token过期时间(通常30分钟-2小时)实现IP绑定或设备指纹等额外验证定期轮换签名密钥7.2 防重放攻击为防止Token被截获后重复使用可以添加jti(JWT ID)声明作为唯一标识维护短期有效的Token使用记录实现nonce机制// 在生成Token时添加jti String jti UUID.randomUUID().toString(); Jwts.builder() .setId(jti) // 其他声明...7.3 日志与监控完善的日志能帮助快速定位问题Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { long startTime System.currentTimeMillis(); try { // 认证逻辑... } finally { long duration System.currentTimeMillis() - startTime; log.info(JWT认证处理时间: {}ms - {}, duration, request.getRequestURI()); } } }8. 测试策略8.1 单元测试示例测试JWT工具类SpringBootTest public class JwtTokenProviderTest { Autowired private JwtTokenProvider tokenProvider; Test public void testGenerateAndValidateToken() { UserPrincipal userPrincipal UserPrincipal.create( new User(1L, test, testexample.com, password)); Authentication authentication new UsernamePasswordAuthenticationToken( userPrincipal, null, userPrincipal.getAuthorities()); String token tokenProvider.generateToken(authentication); assertTrue(tokenProvider.validateToken(token)); Long userId tokenProvider.getUserIdFromToken(token); assertEquals(1L, userId.longValue()); } }8.2 集成测试示例测试受保护的API端点SpringBootTest(webEnvironment WebEnvironment.RANDOM_PORT) AutoConfigureMockMvc public class AuthControllerITest { Autowired private MockMvc mockMvc; Autowired private JwtTokenProvider tokenProvider; Test public void testAccessProtectedApi() throws Exception { UserPrincipal userPrincipal UserPrincipal.create( new User(1L, test, testexample.com, password)); String token tokenProvider.generateToken( new UsernamePasswordAuthenticationToken( userPrincipal, null, userPrincipal.getAuthorities())); mockMvc.perform(get(/api/protected) .header(Authorization, Bearer token)) .andExpect(status().isOk()); } }8.3 性能测试建议使用JMeter或Gatling进行压力测试重点关注认证接口的TPSToken验证的响应时间高并发下的错误率9. 项目结构建议经过多个项目实践我总结出以下推荐的项目结构src/main/java/com/example/ ├── config/ # 配置类 │ ├── SecurityConfig.java │ └── MethodSecurityConfig.java ├── security/ # 安全相关 │ ├── jwt/ # JWT实现 │ │ ├── JwtTokenProvider.java │ │ └── JwtAuthenticationFilter.java │ ├── UserPrincipal.java │ └── CustomUserDetailsService.java ├── model/ # 数据模型 │ ├── Role.java │ ├── Permission.java │ └── User.java ├── repository/ # 数据访问 ├── service/ # 业务服务 ├── controller/ # 控制器 │ ├── AuthController.java │ └── UserController.java └── exception/ # 异常处理 ├── ResourceNotFoundException.java └── GlobalExceptionHandler.java10. 扩展与进阶10.1 多因素认证集成对于高安全要求的系统可以集成短信/邮箱验证码PostMapping(/login/2fa) public ResponseEntity? authenticateWith2fa( Valid RequestBody LoginRequest loginRequest) { // 第一步验证用户名密码 Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsernameOrEmail(), loginRequest.getPassword() ) ); // 生成并发送验证码 String code generateRandomCode(); smsService.sendVerificationCode(loginRequest.getPhone(), code); // 返回临时Token用于第二步验证 String tempToken tokenProvider.generateTempToken(authentication); return ResponseEntity.ok(new TempTokenResponse(tempToken)); }10.2 微服务间的JWT传递在微服务架构中可以通过Feign拦截器自动传递JWTpublic class FeignClientInterceptor implements RequestInterceptor { Override public void apply(RequestTemplate template) { RequestAttributes requestAttributes RequestContextHolder.getRequestAttributes(); if (requestAttributes instanceof ServletRequestAttributes) { HttpServletRequest request ((ServletRequestAttributes)requestAttributes).getRequest(); String token request.getHeader(Authorization); if (token ! null) { template.header(Authorization, token); } } } }10.3 与OAuth2集成对于需要第三方登录的场景可以集成Spring Security OAuth2Configuration EnableAuthorizationServer public class OAuth2Config extends AuthorizationServerConfigurerAdapter { Autowired private AuthenticationManager authenticationManager; Autowired private JwtTokenProvider tokenProvider; Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints .authenticationManager(authenticationManager) .tokenStore(tokenStore()) .accessTokenConverter(accessTokenConverter()); } Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter new JwtAccessTokenConverter(); converter.setSigningKey(jwtSecret); return converter; } Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } }11. 部署注意事项11.1 密钥管理生产环境中我建议使用环境变量或密钥管理服务存储JWT密钥定期轮换签名密钥不同环境使用不同密钥# application-prod.properties jwt.secret${JWT_SECRET} jwt.expiration360000011.2 容器化部署Docker部署时注意安全配置FROM openjdk:17-jdk-slim ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENV JWT_SECRET ENTRYPOINT [java,-Djava.security.egdfile:/dev/./urandom,-jar,/app.jar]11.3 性能调优根据我的经验以下参数对性能影响较大连接池大小JVM内存配置Tomcat线程池配置缓存策略# application.properties server.tomcat.max-threads200 spring.datasource.hikari.maximum-pool-size2012. 监控与维护12.1 健康检查端点添加安全相关的健康检查RestController RequestMapping(/api/health) public class HealthController { GetMapping(/auth) public ResponseEntity? checkAuthHealth() { // 检查认证服务状态 return ResponseEntity.ok().build(); } }12.2 审计日志记录重要安全事件Aspect Component public class SecurityAuditAspect { AfterReturning(execution(* com.example.controller.AuthController.*(..))) public void auditAuthEvent(JoinPoint joinPoint) { String methodName joinPoint.getSignature().getName(); Object[] args joinPoint.getArgs(); // 记录到审计日志 } }12.3 定期安全评估建议定期进行静态代码分析依赖项漏洞扫描渗透测试安全配置审查13. 项目演进建议随着业务发展认证系统可能需要支持多因素认证集成生物识别实现风险自适应认证支持无密码登录区块链身份验证我在实际项目中演进这套系统时保持以下原则向后兼容渐进式改进完善的文档平滑的迁移路径14. 经验总结与避坑指南经过多个项目实践我总结了以下经验教训Token存储问题客户端存储Token时避免使用localStorage易受XSS攻击优先考虑httpOnly的Cookie密钥管理绝对不要将签名密钥硬编码在代码中也避免提交到版本控制系统过期时间Access Token和Refresh Token的过期时间要合理设置通常分别为1小时和7天注销处理JWT本身是无状态的要实现立即注销需要额外机制如短期的黑名单权限粒度RBAC的权限设计要合理避免过度细化导致管理复杂测试覆盖特别注意边界条件的测试如过期Token、篡改Token、空Token等情况错误信息认证失败时返回适当的HTTP状态码但避免泄露过多系统信息性能考量JWT体积会随着claims增加而变大注意控制claims数量跨域问题正确配置CORS同时注意CSRF防护的取舍文档维护保持API文档与安全要求的同步更新15. 完整示例代码结构以下是经过多个项目验证的推荐实现方式├── src/main/java/com/example/ │ ├── config/ │ │ ├── SecurityConfig.java # 主安全配置 │ │ ├── MethodSecurityConfig.java # 方法级安全 │ │ └── CorsConfig.java # 跨域配置 │ ├── security/ │ │ ├── jwt/ │ │ │ ├── JwtTokenProvider.java # JWT核心逻辑 │ │ │ └── JwtAuthenticationFilter.java # 请求过滤器 │ │ ├── UserPrincipal.java # 用户主体 │ │ └── CustomUserDetailsService.java # 用户详情服务 │ ├── model/ │ │ ├── Role.java # 角色实体 │ │ ├── Permission.java # 权限实体 │ │ └── User.java # 用户实体 │ ├── repository/ # 数据访问层 │ ├── service/ # 业务服务层 │ ├── controller/ │ │ ├── AuthController.java # 认证相关端点 │ │ └── UserController.java # 用户管理端点 │ ├── payload/ # 请求/响应DTO │ ├── exception/ # 异常处理 │ └── SpringSecurityJwtApplication.java # 启动类 ├── src/main/resources/ │ ├── application.properties # 基础配置 │ └── application-dev.properties # 开发环境配置 └── src/test/java/ # 测试代码这种结构清晰分离了关注点便于维护和扩展。在实际项目中我通常会根据团队规范和项目规模进行适当调整但核心的安全相关组件保持独立。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Python并发编程实战:多线程与进程池优化指南 2026/9/12 13:15:11

Python并发编程实战:多线程与进程池优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
数据可视化库企业级选型实操指南:Highcharts、ECharts、Plotly等六大库深度压测 2026/9/12 13:15:11

数据可视化库企业级选型实操指南:Highcharts、ECharts、Plotly等六大库深度压测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI写作诊断法:精准定位高考作文失分点 2026/9/12 13:15:11

AI写作诊断法:精准定位高考作文失分点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
股权激励方案设计:利润对赌与长期价值绑定 2026/9/12 13:15:11

股权激励方案设计:利润对赌与长期价值绑定

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
工业项目为何偏爱TCP以太网温湿度传感器?选型与调试实战解析 2026/9/12 13:15:11

工业项目为何偏爱TCP以太网温湿度传感器?选型与调试实战解析

我当时在一个自动化改造项目的选型会上,甲方工程师反复强调一句:“温湿度传感器必须是TCP协议、以太网口,最好带Modbus TCP。”底下有人小声嘀咕:不就是测个温度和湿度嘛,RS485不也一样能读?现场那么长一段…

阅读更多 →
Repomix Explorer Skill 实战指南:让 Claude Code 等 AI 助手用自然语言探索任意代码仓库 2026/9/12 13:12:11

Repomix Explorer Skill 实战指南:让 Claude Code 等 AI 助手用自然语言探索任意代码仓库

Repomix Explorer Skill 实战指南:让 Claude Code 等 AI 助手用自然语言探索任意代码仓库 【免费下载链接】repomix 📦 Repomix is a powerful tool that packs your entire repository into a single, AI-friendly file. Perfect for when you need to …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞