feat: 实现刷新token逻辑

This commit is contained in:
2025-11-20 23:13:33 +08:00
parent c03876e29e
commit 3603d450e8
13 changed files with 146 additions and 77 deletions

View File

@@ -96,5 +96,11 @@
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>4.3.0</version>
</dependency>
<!-- 引入redis存储refreshToken-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -10,4 +10,7 @@ import org.springframework.web.bind.annotation.RequestParam;
public interface UserClient {
@GetMapping("/inner/get-by-username")
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
@GetMapping("/inner/get-by-userid")
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userid);
}

View File

@@ -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
}

View File

@@ -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<UserLoginResponseDTO> login(@RequestBody UserLoginRequestDTO userLoginRequest) {
UserLoginResponseDTO userLoginResponse = authService.userLogin(userLoginRequest);
@@ -27,9 +22,16 @@ public class AuthController {
}
@PostMapping("/refresh")
public Result<UserLoginResponseDTO> refresh(@RequestParam String refreshToken) {
return Results.success(authService.refreshToken(refreshToken));
}
@PostMapping("/auth")
public Result<String> auth(@RequestBody UserLoginRequestDTO userLoginRequest) {
UserLoginResponseDTO userLoginResponseDTO = authService.userLogin(userLoginRequest);
return Results.success(userLoginResponseDTO.getToken());
return Results.success(userLoginResponseDTO.getAccessToken());
}
}

View File

@@ -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;
}

View File

@@ -11,4 +11,10 @@ public interface AuthService {
*/
UserLoginResponseDTO userLogin(UserLoginRequestDTO request);
/**
* 刷新token
* @param refreshToken
* @return
*/
UserLoginResponseDTO refreshToken(String refreshToken);
}

View File

@@ -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<UserAuthRespDTO> 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;
}
}

View File

@@ -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<String, Object> 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;
}
}

View File

@@ -1,4 +1,9 @@
spring:
data:
redis:
host: 10.0.0.10
port: 6379
password: 123456
cloud:
nacos:
discovery:

View File

@@ -34,4 +34,5 @@ mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
jwt:
secret: "12345678901234567890123456789012" # 至少32字节
expire: 86400000 # 24小时单位毫秒
access-expire: 900000 # 24小时
refresh-expire: 604800000 # 7天

View File

@@ -28,5 +28,10 @@ public class UserController {
return Results.success(userAuthDTO);
}
@GetMapping("/inner/get-by-userid")
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userid) {
UserAuthRespDTO userAuthRespDTO = userService.findAuthInfoByUserId(userid);
return Results.success(userAuthRespDTO);
}
}

View File

@@ -22,4 +22,9 @@ public interface UserService extends IService<User> {
* @return
*/
UserAuthRespDTO findAuthInfoByUserAccount(String userAccount);
/**
* 根据用户id查找用户认证信息
*/
UserAuthRespDTO findAuthInfoByUserId(String userId);
}

View File

@@ -65,4 +65,16 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
}
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;
}
}