From 3603d450e84eb1a58c4eda90385864ea19575b0a Mon Sep 17 00:00:00 2001 From: meowrain Date: Thu, 20 Nov 2025 23:13:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=88=B7=E6=96=B0tok?= =?UTF-8?q?en=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aioj-backend-auth/pom.xml | 6 ++ .../aioj/backend/auth/clients/UserClient.java | 3 + .../JwtPropertiesConfiguration.java | 8 ++- .../auth/controller/AuthController.java | 20 +++--- .../auth/dto/resp/UserLoginResponseDTO.java | 49 +-------------- .../backend/auth/service/AuthService.java | 6 ++ .../auth/service/impl/AuthServiceImpl.java | 62 ++++++++++++++++--- .../aioj/backend/auth/utils/JwtUtil.java | 33 +++++++--- .../src/main/resources/application-dev.yml | 5 ++ .../src/main/resources/application.yml | 3 +- .../controller/UserController.java | 5 ++ .../userservice/service/UserService.java | 5 ++ .../service/impl/UserServiceImpl.java | 18 +++++- 13 files changed, 146 insertions(+), 77 deletions(-) rename aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/{ => properties}/JwtPropertiesConfiguration.java (69%) diff --git a/aioj-backend-auth/pom.xml b/aioj-backend-auth/pom.xml index ce205b0..0006c6b 100644 --- a/aioj-backend-auth/pom.xml +++ b/aioj-backend-auth/pom.xml @@ -96,5 +96,11 @@ spring-cloud-starter-loadbalancer 4.3.0 + + + + org.springframework.boot + spring-boot-starter-data-redis + \ No newline at end of file diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java index 3da69ce..6b77060 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/clients/UserClient.java @@ -10,4 +10,7 @@ import org.springframework.web.bind.annotation.RequestParam; public interface UserClient { @GetMapping("/inner/get-by-username") Result getUserByUserName(@RequestParam("userAccount") String userAccount); + + @GetMapping("/inner/get-by-userid") + public Result getUserById(@RequestParam("userId") String userid); } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/JwtPropertiesConfiguration.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/properties/JwtPropertiesConfiguration.java similarity index 69% rename from aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/JwtPropertiesConfiguration.java rename to aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/properties/JwtPropertiesConfiguration.java index eb5b4cc..5dfd246 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/JwtPropertiesConfiguration.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/config/properties/JwtPropertiesConfiguration.java @@ -1,4 +1,4 @@ -package cn.meowrain.aioj.backend.auth.config; +package cn.meowrain.aioj.backend.auth.config.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -17,5 +17,9 @@ public class JwtPropertiesConfiguration { /** * 过期时间(单位:毫秒) */ - private Long expire; + private long accessExpire; // access token TTL + /** + * 刷新令牌时间 + */ + private long refreshExpire; // refresh token TTL } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java index ccf7337..bf8b590 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/controller/AuthController.java @@ -5,21 +5,16 @@ import cn.meowrain.aioj.backend.auth.dto.resp.UserLoginResponseDTO; import cn.meowrain.aioj.backend.auth.service.AuthService; import cn.meowrain.aioj.backend.framework.web.Results; import cn.meowrain.aioj.backend.framework.web.Result; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; @RestController +@RequiredArgsConstructor @RequestMapping("/v1/auth") public class AuthController { private final AuthService authService; - public AuthController(AuthService authService) { - this.authService = authService; - } - @PostMapping("/login") public Result login(@RequestBody UserLoginRequestDTO userLoginRequest) { UserLoginResponseDTO userLoginResponse = authService.userLogin(userLoginRequest); @@ -27,9 +22,16 @@ public class AuthController { } + @PostMapping("/refresh") + public Result refresh(@RequestParam String refreshToken) { + return Results.success(authService.refreshToken(refreshToken)); + } + @PostMapping("/auth") public Result auth(@RequestBody UserLoginRequestDTO userLoginRequest) { UserLoginResponseDTO userLoginResponseDTO = authService.userLogin(userLoginRequest); - return Results.success(userLoginResponseDTO.getToken()); + return Results.success(userLoginResponseDTO.getAccessToken()); } + + } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/resp/UserLoginResponseDTO.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/resp/UserLoginResponseDTO.java index d9f0d7e..ad057f7 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/resp/UserLoginResponseDTO.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/dto/resp/UserLoginResponseDTO.java @@ -22,52 +22,9 @@ public class UserLoginResponseDTO implements Serializable { */ private String unionId; - /** - * 公众号openId - */ - private String mpOpenId; - - /** - * 用户昵称 - */ - private String userName; - - /** - * 用户头像 - */ - private String userAvatar; - - /** - * 用户简介 - */ - private String userProfile; - - /** - * 用户角色:user/admin/ban - */ - private String userRole; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * 更新时间 - */ - private Date updateTime; - - /** - * 是否删除 - */ - - private Integer isDelete; - - - /** - * JWT令牌(登录成功返回) - */ - private String token; + private String accessToken; + private String refreshToken; + private Long expire; private static final long serialVersionUID = 1L; } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java index 92cf15c..a3ef754 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/AuthService.java @@ -11,4 +11,10 @@ public interface AuthService { */ UserLoginResponseDTO userLogin(UserLoginRequestDTO request); + /** + * 刷新token + * @param refreshToken + * @return + */ + UserLoginResponseDTO refreshToken(String refreshToken); } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java index f1aaf7b..0113b09 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/service/impl/AuthServiceImpl.java @@ -3,7 +3,9 @@ package cn.meowrain.aioj.backend.auth.service.impl; import cn.hutool.core.util.ObjectUtil; import cn.hutool.crypto.digest.BCrypt; import cn.meowrain.aioj.backend.auth.clients.UserClient; +import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants; import cn.meowrain.aioj.backend.auth.common.enums.ChainMarkEnums; +import cn.meowrain.aioj.backend.auth.config.properties.JwtPropertiesConfiguration; import cn.meowrain.aioj.backend.auth.dto.chains.context.UserLoginRequestParamVerifyContext; import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO; import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO; @@ -15,8 +17,11 @@ import cn.meowrain.aioj.backend.framework.exception.ServiceException; import cn.meowrain.aioj.backend.framework.web.Result; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; +import java.util.concurrent.TimeUnit; + @Component @RequiredArgsConstructor @Slf4j @@ -24,6 +29,8 @@ public class AuthServiceImpl implements AuthService { private final JwtUtil jwtUtil; private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext; private final UserClient userClient; + private final StringRedisTemplate stringRedisTemplate; + private final JwtPropertiesConfiguration jwtPropertiesConfiguration; @Override public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) { @@ -43,16 +50,57 @@ public class AuthServiceImpl implements AuthService { throw new ServiceException("用户不存在或者密码错误", ErrorCode.NOT_LOGIN_ERROR); } // 生成 JWT - String token = jwtUtil.generateToken(user); + String accessToken = jwtUtil.generateAccessToken(user); + String refreshToken = jwtUtil.generateRefreshToken(user.getId()); UserLoginResponseDTO resp = new UserLoginResponseDTO(); resp.setId(user.getId()); resp.setUserAccount(user.getUserAccount()); - resp.setUserAvatar(user.getUserAvatar()); - resp.setUserProfile(user.getUserProfile()); - resp.setUserRole(user.getUserRole()); - resp.setCreateTime(user.getCreateTime()); - resp.setUpdateTime(user.getUpdateTime()); - resp.setToken(token); + resp.setAccessToken(accessToken); + resp.setRefreshToken(refreshToken); + + // refresh token存入到REDIS里面 + stringRedisTemplate.opsForValue().set( + String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()), + refreshToken, + jwtPropertiesConfiguration.getRefreshExpire(), + TimeUnit.MILLISECONDS); return resp; } + + /** + * 更新access token,使用refresh token + * @param refreshToken + * @return + */ + @Override + public UserLoginResponseDTO refreshToken(String refreshToken) { + UserLoginResponseDTO userLoginResponseDTO = new UserLoginResponseDTO(); + if (!jwtUtil.isTokenValid(refreshToken)) { + throw new RuntimeException("Refresh Token 已过期"); + } + + Long userId = Long.valueOf(jwtUtil.parseClaims(refreshToken).getSubject()); + + String cacheKey = String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, userId); + String cacheValue = stringRedisTemplate.opsForValue().get(cacheKey); + + if (cacheValue == null || !cacheValue.equals(refreshToken)) { + throw new RuntimeException("Refresh Token 已失效"); + } + + // 再次签发新的 Access Token + // 此处你需要查用户,拿 userName, role + Result userResult = userClient.getUserById(String.valueOf(userId)); + if (userResult.isFail()) { + log.error("通过id查找用户失败:{}", userResult.getMessage()); + throw new ServiceException(ErrorCode.SYSTEM_ERROR); + } + UserAuthRespDTO user = userResult.getData(); + String newAccessToken = jwtUtil.generateAccessToken(user); + + //设置refresh token和access token + userLoginResponseDTO.setRefreshToken(refreshToken); + userLoginResponseDTO.setAccessToken(newAccessToken); + return userLoginResponseDTO; + } } diff --git a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/utils/JwtUtil.java b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/utils/JwtUtil.java index 2382d65..6a361be 100644 --- a/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/utils/JwtUtil.java +++ b/aioj-backend-auth/src/main/java/cn/meowrain/aioj/backend/auth/utils/JwtUtil.java @@ -1,6 +1,6 @@ package cn.meowrain.aioj.backend.auth.utils; -import cn.meowrain.aioj.backend.auth.config.JwtPropertiesConfiguration; +import cn.meowrain.aioj.backend.auth.config.properties.JwtPropertiesConfiguration; import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -13,32 +13,47 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -/** - * JWT工具类 - */ @RequiredArgsConstructor @Component public class JwtUtil { + private final JwtPropertiesConfiguration jwtConfig; private SecretKey getSigningKey() { return Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes()); } - public String generateToken(UserAuthRespDTO user) { + /** 生成 Access Token */ + public String generateAccessToken(UserAuthRespDTO user) { long now = System.currentTimeMillis(); + Map claims = new HashMap<>(); claims.put("userId", user.getId()); + claims.put("userName", user.getUserName()); claims.put("role", user.getUserRole()); + return Jwts.builder() .subject(user.getUserAccount()) .issuedAt(new Date(now)) - .expiration(new Date(now + jwtConfig.getExpire())) + .expiration(new Date(now + jwtConfig.getAccessExpire())) .claims(claims) .signWith(getSigningKey(), Jwts.SIG.HS256) .compact(); } + /** 生成 Refresh Token(只含 userId) */ + public String generateRefreshToken(Long userId) { + long now = System.currentTimeMillis(); + + return Jwts.builder() + .subject(String.valueOf(userId)) + .issuedAt(new Date(now)) + .expiration(new Date(now + jwtConfig.getRefreshExpire())) + .signWith(getSigningKey(), Jwts.SIG.HS256) + .compact(); + } + + /** 解析 Token */ public Claims parseClaims(String token) { return Jwts.parser() .verifyWith(getSigningKey()) @@ -47,12 +62,12 @@ public class JwtUtil { .getPayload(); } + /** 校验 Token 是否过期 */ public boolean isTokenValid(String token) { try { Claims claims = parseClaims(token); - Date expiration = claims.getExpiration(); - return expiration != null && expiration.after(new Date()); - } catch (Exception e) { + return claims.getExpiration().after(new Date()); + } catch (Exception ignored) { return false; } } diff --git a/aioj-backend-auth/src/main/resources/application-dev.yml b/aioj-backend-auth/src/main/resources/application-dev.yml index 94b9104..c817fc0 100644 --- a/aioj-backend-auth/src/main/resources/application-dev.yml +++ b/aioj-backend-auth/src/main/resources/application-dev.yml @@ -1,4 +1,9 @@ spring: + data: + redis: + host: 10.0.0.10 + port: 6379 + password: 123456 cloud: nacos: discovery: diff --git a/aioj-backend-auth/src/main/resources/application.yml b/aioj-backend-auth/src/main/resources/application.yml index 571f68a..b60a8b4 100644 --- a/aioj-backend-auth/src/main/resources/application.yml +++ b/aioj-backend-auth/src/main/resources/application.yml @@ -34,4 +34,5 @@ mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml jwt: secret: "12345678901234567890123456789012" # 至少32字节!! - expire: 86400000 # 24小时(单位:毫秒) \ No newline at end of file + access-expire: 900000 # 24小时 + refresh-expire: 604800000 # 7天 \ No newline at end of file diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java index bbb47d9..d1b87e4 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/controller/UserController.java @@ -28,5 +28,10 @@ public class UserController { return Results.success(userAuthDTO); } + @GetMapping("/inner/get-by-userid") + public Result getUserById(@RequestParam("userId") String userid) { + UserAuthRespDTO userAuthRespDTO = userService.findAuthInfoByUserId(userid); + return Results.success(userAuthRespDTO); + } } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java index 7fdf0d4..837105e 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/UserService.java @@ -22,4 +22,9 @@ public interface UserService extends IService { * @return */ UserAuthRespDTO findAuthInfoByUserAccount(String userAccount); + + /** + * 根据用户id查找用户认证信息 + */ + UserAuthRespDTO findAuthInfoByUserId(String userId); } diff --git a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java index 7bc4fab..e69f9e6 100644 --- a/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java +++ b/aioj-backend-user-service/src/main/java/cn/meowrain/aioj/backend/userservice/service/impl/UserServiceImpl.java @@ -30,7 +30,7 @@ public class UserServiceImpl extends ServiceImpl implements Us @Override public Long userRegister(UserRegisterRequestDTO request) { UserAuthRespDTO authInfoByUserAccount = findAuthInfoByUserAccount(request.getUserAccount()); - if(authInfoByUserAccount!=null){ + if (authInfoByUserAccount != null) { throw new ClientException("重复创建用户"); } @@ -40,7 +40,7 @@ public class UserServiceImpl extends ServiceImpl implements Us // 使用 BCrypt 加密密码 Date now = new Date(); String salt = BCrypt.gensalt(); - String encryptPassword = BCrypt.hashpw(request.getUserPassword(),salt); + String encryptPassword = BCrypt.hashpw(request.getUserPassword(), salt); User user = new User().setUserAccount(request.getUserAccount()).setUserPassword(encryptPassword) .setUserRole("user").setCreateTime(now).setUpdateTime(now); try { @@ -59,10 +59,22 @@ public class UserServiceImpl extends ServiceImpl implements Us public UserAuthRespDTO findAuthInfoByUserAccount(String userAccount) { User one = this.lambdaQuery().eq(User::getUserAccount, userAccount).one(); UserAuthRespDTO userAuthDTO = new UserAuthRespDTO(); - if(one!=null){ + if (one != null) { BeanUtils.copyProperties(one, userAuthDTO); return userAuthDTO; } return null; } + + @Override + public UserAuthRespDTO findAuthInfoByUserId(String userId) { + User one = this.lambdaQuery().eq(User::getId, userId).one(); + UserAuthRespDTO userAuthDTO = new UserAuthRespDTO(); + if (one != null) { + BeanUtils.copyProperties(one, userAuthDTO); + return userAuthDTO; + } + return null; + + } }