From 91db1899e021a1518f7c5cf2524652319cb516bb Mon Sep 17 00:00:00 2001 From: weitang Date: Fri, 30 May 2025 17:23:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A2=B3=E7=90=86=E7=99=BB=E5=BD=95token?= =?UTF-8?q?=E9=89=B4=E6=9D=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/JwtAuthenticationFilter.java | 2 +- .../stdproject/controller/AuthController.java | 70 +++++++++++-------- .../java/com/stdproject/utils/JwtUtils.java | 32 ++++++++- backend/src/main/resources/application.yml | 4 +- 4 files changed, 73 insertions(+), 35 deletions(-) diff --git a/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java b/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java index a077d7e..5e277fb 100644 --- a/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java @@ -75,4 +75,4 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } return null; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AuthController.java b/backend/src/main/java/com/stdproject/controller/AuthController.java index ab6ca07..dff7e05 100644 --- a/backend/src/main/java/com/stdproject/controller/AuthController.java +++ b/backend/src/main/java/com/stdproject/controller/AuthController.java @@ -56,13 +56,13 @@ public class AuthController { public static class LoginRequest { @NotBlank(message = "用户名不能为空") private String username; - + @NotBlank(message = "密码不能为空") private String password; - + @NotBlank(message = "验证码不能为空") private String captcha; - + @NotBlank(message = "验证码Key不能为空") private String captchaKey; @@ -83,7 +83,7 @@ public class AuthController { public static class ChangePasswordRequest { @NotBlank(message = "原密码不能为空") private String oldPassword; - + @NotBlank(message = "新密码不能为空") private String newPassword; @@ -103,18 +103,18 @@ public class AuthController { try { // 生成验证码 CaptchaUtils.CaptchaResult captchaResult = captchaUtils.generateCaptcha(); - + // 生成验证码Key String captchaKey = UUID.randomUUID().toString(); - + // 注意:验证码应当存储在会话中或其他存储中,此处省略存储步骤 // 在实际应用中,可以使用Session或其他方式存储验证码 - + Map result = new HashMap<>(); result.put("captchaKey", captchaKey); result.put("captchaImage", captchaResult.getImageBase64()); result.put("code", captchaResult.getCode()); // 临时方案:直接返回验证码(生产环境不建议) - + return Result.success(result); } catch (Exception e) { log.error("生成验证码失败: {}", e.getMessage(), e); @@ -133,7 +133,7 @@ public class AuthController { // 注意:在实际应用中,应该从会话或其他存储中获取验证码进行验证 // 此处简化处理,假设验证码已通过(实际应用中需要实现验证逻辑) // 如果使用了临时方案,可以从前端传回验证码进行比对 - + // 进行身份认证 Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( @@ -144,16 +144,16 @@ public class AuthController { AppUser user=appUserService.findByUsername(loginRequest.getUsername()); // 设置认证信息到安全上下文 SecurityContextHolder.getContext().setAuthentication(authentication); - + // 生成JWT Token - String token = jwtUtils.generateToken(user.getUsername(),user.getId()); - + String token = jwtUtils.generateToken(user.getUsername(),user.getId()); + Map result = new HashMap<>(); result.put("token", token); result.put("userInfo", user); - + return Result.success(result); - + } catch (Exception e) { log.error("用户登录失败: {}", e.getMessage(), e); return Result.error("登录失败: " + e.getMessage()); @@ -172,15 +172,15 @@ public class AuthController { String token = request.getHeader(Constants.JWT_HEADER); if (token != null && token.startsWith(Constants.JWT_PREFIX)) { token = token.substring(Constants.JWT_PREFIX.length()); - + // 注意:在实际应用中,应该实现Token失效机制 // 可以考虑使用短期Token或其他方式实现Token失效 // 此处省略Token黑名单实现 } - + // 清除安全上下文 SecurityContextHolder.clearContext(); - + return Result.success(); } catch (Exception e) { log.error("用户登出失败: {}", e.getMessage(), e); @@ -198,11 +198,11 @@ public class AuthController { String token = request.getHeader(Constants.JWT_HEADER); if (token != null && token.startsWith(Constants.JWT_PREFIX)) { token = token.substring(Constants.JWT_PREFIX.length()); - String username = jwtUtils.getUsernameFromToken(token); + String username = jwtUtils.getUsernameFromToken(token); AppUser userInfo = appUserService.findByUsername(username); return Result.success(userInfo); } - + return Result.unauthorized(); } catch (Exception e) { log.error("获取用户信息失败: {}", e.getMessage(), e); @@ -222,7 +222,7 @@ public class AuthController { if (token != null && token.startsWith(Constants.JWT_PREFIX)) { token = token.substring(Constants.JWT_PREFIX.length()); String username = jwtUtils.getUsernameFromToken(token); - + boolean success = appUserService.changePassword(username, request.getOldPassword(), request.getNewPassword()); if (success) { return Result.success(); @@ -230,29 +230,37 @@ public class AuthController { return Result.error("原密码错误"); } } - + return Result.unauthorized(); } catch (Exception e) { log.error("修改密码失败: {}", e.getMessage(), e); return Result.error("修改密码失败"); } } - + @PostMapping("/refreshToken") public Result refreshToken(@RequestBody Map params) { String refreshToken = params.get("refreshToken"); + if (refreshToken == null || refreshToken.trim().isEmpty()) { return Result.error("刷新令牌不能为空"); } - // 验证refreshToken的有效性并获取用户信息 - String username = jwtUtils.getUsernameFromToken(refreshToken); - if (username != null) { - AppUser user = appUserService.findByUsername(username); - // 生成新的token - return Result.success(jwtUtils.generateToken(user.getUsername(), user.getId())); + + try { + String username = jwtUtils.getUsernameFromToken(refreshToken); + + if (username != null && jwtUtils.validateToken(refreshToken, username)) { + AppUser user = appUserService.findByUsername(username); + String newAccessToken = jwtUtils.generateRefreshToken(user.getUsername(), user.getId()); + return Result.success(newAccessToken); + } else { + return Result.error("刷新令牌已失效"); + } + } catch (Exception e) { + log.error("刷新 Token 失败: {}", e.getMessage()); + return Result.error("刷新 Token 失败"); } - return Result.error("无效的刷新令牌"); } - -} \ No newline at end of file + +} diff --git a/backend/src/main/java/com/stdproject/utils/JwtUtils.java b/backend/src/main/java/com/stdproject/utils/JwtUtils.java index f1a0238..4f6382d 100644 --- a/backend/src/main/java/com/stdproject/utils/JwtUtils.java +++ b/backend/src/main/java/com/stdproject/utils/JwtUtils.java @@ -28,6 +28,9 @@ public class JwtUtils { @Value("${spring.security.jwt.expiration-ms}") private Long expirationMs; + @Value("${spring.security.jwt.refresh-expiration-ms}") + private Long refreshExpirationMs; + /** * 生成JWT令牌 * @@ -62,6 +65,33 @@ public class JwtUtils { .compact(); } + /** + * 创建带刷新时间的 Token + */ + private String createRefreshToken(Map claims, String subject) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + refreshExpirationMs); + + return Jwts.builder() + .setClaims(claims) + .setSubject(subject) + .setIssuedAt(now) + .setExpiration(expiryDate) + .signWith(getSigningKey(), SignatureAlgorithm.HS512) + .compact(); + } + + /** + * 生成 Refresh Token + */ + public String generateRefreshToken(String username, String userId) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + return createRefreshToken(claims, username); + } + + /** * 从JWT令牌中获取用户名 * @@ -165,4 +195,4 @@ public class JwtUtils { public interface ClaimsResolver { T resolve(Claims claims); } -} \ No newline at end of file +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 8662cc9..d006c7c 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -55,7 +55,7 @@ spring: jwt: enabled: ${JWT_ENABLED:true} # 控制是否启用JWT认证 secret: ${JWT_SECRET:YourJWTSecretKeyForStdProjectBackendApplicationWhichIsVeryLongAndSecure2024!@#$%^&*()} - expiration-ms: ${JWT_EXPIRATION:86400000} # Token 过期时间 (例如: 24小时) + expiration-ms: ${JWT_EXPIRATION:1800000} # Token 过期时间 (例如: 24小时) refresh-expiration-ms: ${JWT_REFRESH_EXPIRATION:604800000} # 刷新Token过期时间 (例如: 7天) mybatis-plus: @@ -155,7 +155,7 @@ logging: org.hibernate.type.descriptor.sql.BasicBinder: TRACE mybatis-plus: configuration: - log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl #org.apache.ibatis.logging.stdout.StdOutImpl springdoc: swagger-ui: enabled: true