diff --git a/xs-api/src/main/java/com/xiang/xservice/auth/api/code/Code01UserErrorCode.java b/xs-api/src/main/java/com/xiang/xservice/auth/api/code/Code01UserErrorCode.java index 796fac0..7334596 100644 --- a/xs-api/src/main/java/com/xiang/xservice/auth/api/code/Code01UserErrorCode.java +++ b/xs-api/src/main/java/com/xiang/xservice/auth/api/code/Code01UserErrorCode.java @@ -12,6 +12,7 @@ public enum Code01UserErrorCode implements BaseErrorCode { USER_EXISTS("A1000102", "用户已存在"), USER_LOGIN_ERROR("A1000103", "用户登录失败!"), USER_REGISTER_ERROR("1000104", "用户注册失败!"), + REFRESH_TOKEN_NOT_EXISTS("1000105", "refreshToken不匹配") ; private final String code; diff --git a/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/req/RefreshRequest.java b/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/req/RefreshRequest.java new file mode 100644 index 0000000..66098fd --- /dev/null +++ b/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/req/RefreshRequest.java @@ -0,0 +1,25 @@ +package com.xiang.xservice.auth.api.dto.req; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @Author: xiang + * @Date: 2026-03-20 13:46 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RefreshRequest { + + /** + * 用户名 + */ + private String username; + + /** + * refresh Token + */ + private String refreshToken; +} diff --git a/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/resp/LoginResp.java b/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/resp/LoginResp.java index 0ad934f..cd39b2c 100644 --- a/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/resp/LoginResp.java +++ b/xs-api/src/main/java/com/xiang/xservice/auth/api/dto/resp/LoginResp.java @@ -12,4 +12,6 @@ public class LoginResp { private String username; private String token; + + private String refreshToken; } diff --git a/xs-server/src/main/java/com/xiang/xservice/auth/server/controller/TokenController.java b/xs-server/src/main/java/com/xiang/xservice/auth/server/controller/TokenController.java index a730a10..7a02afa 100644 --- a/xs-server/src/main/java/com/xiang/xservice/auth/server/controller/TokenController.java +++ b/xs-server/src/main/java/com/xiang/xservice/auth/server/controller/TokenController.java @@ -2,6 +2,7 @@ package com.xiang.xservice.auth.server.controller; import com.xiang.xservice.auth.api.api.TokenApi; import com.xiang.xservice.auth.api.dto.req.LoginRequest; +import com.xiang.xservice.auth.api.dto.req.RefreshRequest; import com.xiang.xservice.auth.api.dto.req.RegisterRequest; import com.xiang.xservice.auth.api.dto.resp.LoginResp; import com.xiang.xservice.auth.api.dto.resp.RegisterResp; @@ -41,6 +42,20 @@ public class TokenController implements TokenApi { } } + @PostMapping("/publish/auth/refresh") + public Result refresh(@RequestBody @NotNull(message = "请求参数不能为空") @Valid RefreshRequest request) { + try { + LoginResp login = userService.refresh(request); + return Result.data(login); + } catch (BusinessException e) { + log.error("【用户登录】用户登录失败,{}", e.getMessage(), e); + return Result.error(e.getMessage()); + } catch (Exception e) { + log.error("【用户登录】用户登录失败,{}", e.getMessage(), e); + return Result.error(); + } + } + @PostMapping("/public/user/userRegister") public Result register(@RequestBody @Valid @NotNull(message = "请求参数不能为空") RegisterRequest request) { diff --git a/xs-service/src/main/java/com/xiang/xservice/auth/service/service/XUserService.java b/xs-service/src/main/java/com/xiang/xservice/auth/service/service/XUserService.java index eef2726..d41a52e 100644 --- a/xs-service/src/main/java/com/xiang/xservice/auth/service/service/XUserService.java +++ b/xs-service/src/main/java/com/xiang/xservice/auth/service/service/XUserService.java @@ -2,6 +2,7 @@ package com.xiang.xservice.auth.service.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.xiang.xservice.auth.api.dto.req.LoginRequest; +import com.xiang.xservice.auth.api.dto.req.RefreshRequest; import com.xiang.xservice.auth.api.dto.req.RegisterRequest; import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest; import com.xiang.xservice.auth.api.dto.req.user.UserDeptUpdateRequest; @@ -38,4 +39,6 @@ public interface XUserService { Boolean setUserRole(UserRoleUpdateRequest request); UserDTO getUserDetail(Long userId); + + LoginResp refresh(RefreshRequest request); } diff --git a/xs-service/src/main/java/com/xiang/xservice/auth/service/service/impl/XUserServiceImpl.java b/xs-service/src/main/java/com/xiang/xservice/auth/service/service/impl/XUserServiceImpl.java index df9c234..ec6ee80 100644 --- a/xs-service/src/main/java/com/xiang/xservice/auth/service/service/impl/XUserServiceImpl.java +++ b/xs-service/src/main/java/com/xiang/xservice/auth/service/service/impl/XUserServiceImpl.java @@ -9,6 +9,7 @@ import com.xiang.xservice.auth.api.code.Code01UserErrorCode; import com.xiang.xservice.auth.api.code.Code02RoleErrorCode; import com.xiang.xservice.auth.api.code.Code03DeptErrorCode; import com.xiang.xservice.auth.api.dto.req.LoginRequest; +import com.xiang.xservice.auth.api.dto.req.RefreshRequest; import com.xiang.xservice.auth.api.dto.req.RegisterRequest; import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest; import com.xiang.xservice.auth.api.dto.req.user.UserDeptUpdateRequest; @@ -123,6 +124,7 @@ public class XUserServiceImpl implements XUserService { LoginResp loginResp = new LoginResp(); loginResp.setToken(user.getToken()); loginResp.setUsername(request.getUsername()); + loginResp.setRefreshToken(user.getRefreshToken()); return loginResp; } } @@ -159,6 +161,7 @@ public class XUserServiceImpl implements XUserService { LoginResp loginResp = new LoginResp(); loginResp.setToken(token); loginResp.setUsername(request.getUsername()); + loginResp.setRefreshToken(refreshToken); // 3. redis缓存token redisService.set(RedisConstant.LOGIN_TOKEN + request.getUsername(), token, 3, TimeUnit.HOURS); // 4. db 存储token @@ -318,4 +321,56 @@ public class XUserServiceImpl implements XUserService { } return dto; } + + @Override + public LoginResp refresh(RefreshRequest request) { + XUser user = userMapper.selectByUsername(request.getUsername()); + if (Objects.isNull(user)) { + throw new BusinessException(Code01UserErrorCode.USER_NOT_EXISTS); + } + if (StringUtils.isBlank(user.getRefreshToken()) || !user.getRefreshToken().equals(request.getRefreshToken())) { + throw new BusinessException(Code01UserErrorCode.REFRESH_TOKEN_NOT_EXISTS); + } + // 校验 refreshToken 是否过期 + Jwt refreshJwt; + try { + refreshJwt = jwtDecoder.decode(request.getRefreshToken()); + } catch (Exception e) { + log.error("【刷新token】refreshToken解析失败", e); + throw new BusinessException("refreshToken 无效或已过期"); + } + if (Objects.isNull(refreshJwt.getExpiresAt()) || refreshJwt.getExpiresAt().isBefore(Instant.now())) { + throw new BusinessException("refreshToken 已过期,请重新登录"); + } + // 查询角色 + List roleCodes = Lists.newArrayList(); + List userRoles = userRoleMapper.getByUserId(user.getId()); + if (CollectionUtils.isNotEmpty(userRoles)) { + List roles = roleMapper.getRoleByIds(userRoles.stream().map(XUserRole::getRoleId).collect(Collectors.toList())); + if (CollectionUtils.isNotEmpty(roles)) { + roleCodes.addAll(roles.stream().map(XRole::getCode).toList()); + } + } + // 生成新的 accessToken + Instant now = Instant.now(); + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuedAt(now) + .expiresAt(now.plus(3, ChronoUnit.HOURS)) + .claim("userId", user.getId()) + .claim("tenantId", user.getTenantId()) + .claim("timestamp", System.currentTimeMillis()) + .claim("username", request.getUsername()) + .claim("authorities", roleCodes) + .build(); + String newToken = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + // 更新 Redis 和 DB + redisService.set(RedisConstant.LOGIN_TOKEN + request.getUsername(), newToken, 3, TimeUnit.HOURS); + user.setToken(newToken); + userMapper.update(user); + LoginResp loginResp = new LoginResp(); + loginResp.setToken(newToken); + loginResp.setUsername(request.getUsername()); + loginResp.setRefreshToken(request.getRefreshToken()); + return loginResp; + } }