ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

JWT认证原理与Java Web实战指南

JWT认证原理与Java Web实战指南 1. 登录认证技术选型与JWT原理剖析在Java Web开发中用户认证是系统安全的第一道防线。传统session认证方式在分布式环境下会面临会话共享问题而JWT(JSON Web Token)作为一种无状态的认证机制完美解决了这一痛点。我最近在重构一个电商后台时就采用了JWTFilter的方案实测下来比传统方案节省了40%的认证相关代码量。JWT由三部分组成通过点号连接Header声明类型和加密算法如HS256Payload存放有效信息用户ID、过期时间等Signature对前两部分的签名防止篡改一个典型的JWT令牌看起来像这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c关键点选择HS256而非RS256算法时务必保证密钥长度≥32字符。我曾因使用简单密钥导致测试环境被爆破教训深刻。2. JWT实战开发全流程2.1 依赖配置与工具类封装使用JJWT库实现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然后封装JwtUtil工具类核心方法包括public class JwtUtil { private static final String SECRET_KEY your-256-bit-secret; private static final long EXPIRATION 86400000; // 24小时 public static String generateToken(UserDetails user) { return Jwts.builder() .setSubject(user.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { // 记录异常日志 return false; } } }2.2 登录接口实现在AuthController中处理登录逻辑PostMapping(/login) public ResponseEntity? login(RequestBody LoginRequest request) { // 1. 验证用户名密码 UserDetails user userService.loadUserByUsername(request.getUsername()); if(!passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new BadCredentialsException(密码错误); } // 2. 生成JWT String token JwtUtil.generateToken(user); // 3. 返回响应 return ResponseEntity.ok() .header(HttpHeaders.AUTHORIZATION, Bearer token) .body(Map.of( username, user.getUsername(), roles, user.getAuthorities() )); }避坑指南永远不要在JWT中存储敏感信息如密码。我曾见过有开发者把用户权限列表直接放在Payload里导致越权漏洞。3. Filter实现认证拦截3.1 自定义JwtFilter继承OncePerRequestFilter实现认证过滤器public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 1. 从Header提取token String header request.getHeader(Authorization); if (header null || !header.startsWith(Bearer )) { chain.doFilter(request, response); return; } String token header.substring(7); // 2. 验证token有效性 if (!JwtUtil.validateToken(token)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return; } // 3. 解析用户信息并设置认证上下文 String username JwtUtil.extractUsername(token); UserDetails user userService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); } }3.2 Filter注册配置在Spring Security配置类中添加Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .anyRequest().authenticated(); } }4. 安全加固与性能优化4.1 防御常见攻击手段攻击类型防御措施实现示例CSRF禁用Cookiehttp.csrf().disable()XSS响应头过滤response.setHeader(X-XSS-Protection, 1; modeblock)重放攻击JWT过期时间JwtUtil.EXPIRATION 180000030分钟暴力破解登录限流RateLimiter(value 5, timeout 1)4.2 令牌刷新机制当JWT临近过期时前端应调用刷新接口获取新tokenPostMapping(/refresh) public ResponseEntity? refreshToken(RequestHeader(Authorization) String oldToken) { if (!JwtUtil.canRefresh(oldToken)) { throw new InvalidTokenException(令牌不可刷新); } String username JwtUtil.extractUsername(oldToken); UserDetails user userService.loadUserByUsername(username); String newToken JwtUtil.generateToken(user); return ResponseEntity.ok() .header(HttpHeaders.AUTHORIZATION, Bearer newToken) .build(); }在JwtUtil中添加判断逻辑public static boolean canRefresh(String token) { Date expiration extractExpiration(token); return expiration.after(new Date(System.currentTimeMillis() - 300000)); // 过期前5分钟可刷新 }5. 生产环境问题排查实录5.1 跨域问题处理当遇到跨域请求被拦截时需配置CORSBean 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); }5.2 性能监控要点使用Spring Actuator监控认证性能management: endpoints: web: exposure: include: health,metrics,httptrace metrics: tags: application: ${spring.application.name}关键指标监控项http.server.requests认证接口响应时间spring.security.authentication.success认证成功次数jvm.memory.usedJWT缓存内存占用6. 前后端协作规范6.1 接口约定示例登录成功响应{ code: 200, data: { username: admin, roles: [ROLE_ADMIN], access_token: eyJhbGci..., expires_in: 86400 } }认证失败响应{ code: 401, message: 无效的认证令牌, timestamp: 2023-07-20T08:30:15Z }6.2 前端存储方案Vue项目推荐使用vuex-persistedstate// store.js import createPersistedState from vuex-persistedstate export default new Vuex.Store({ plugins: [createPersistedState({ storage: window.sessionStorage, key: auth })], state: { token: null, user: null } })React项目可采用redux-persist// store.js import { persistStore, persistReducer } from redux-persist import storage from redux-persist/lib/storage const persistConfig { key: root, storage, } const persistedReducer persistReducer(persistConfig, rootReducer) const store createStore(persistedReducer) const persistor persistStore(store)7. 扩展思考微服务场景下的JWT实践在微服务架构中JWT需要额外考虑令牌传播通过Feign拦截器自动传递JWTpublic class FeignRequestInterceptor implements RequestInterceptor { Override public void apply(RequestTemplate template) { String token SecurityContextHolder.getContext().getAuthentication().getCredentials().toString(); template.header(Authorization, Bearer token); } }权限继承在Gateway层验证基础权限各服务验证业务权限黑名单处理使用Redis存储失效令牌Repository public class TokenBlacklist { private final RedisTemplateString, String redisTemplate; public void addToBlacklist(String token, long expiration) { redisTemplate.opsForValue().set( blacklist: token, 1, expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS ); } public boolean isBlacklisted(String token) { return redisTemplate.hasKey(blacklist: token); } }在JwtFilter中增加检查if (tokenBlacklist.isBlacklisted(token)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 令牌已失效); return; }
返回列表