feat: 实现刷新token逻辑
This commit is contained in:
@@ -96,5 +96,11 @@
|
|||||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>4.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 引入redis,存储refreshToken-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -10,4 +10,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
public interface UserClient {
|
public interface UserClient {
|
||||||
@GetMapping("/inner/get-by-username")
|
@GetMapping("/inner/get-by-username")
|
||||||
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
|
Result<UserAuthRespDTO> getUserByUserName(@RequestParam("userAccount") String userAccount);
|
||||||
|
|
||||||
|
@GetMapping("/inner/get-by-userid")
|
||||||
|
public Result<UserAuthRespDTO> getUserById(@RequestParam("userId") String userid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package cn.meowrain.aioj.backend.auth.config;
|
package cn.meowrain.aioj.backend.auth.config.properties;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
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
|
||||||
}
|
}
|
||||||
@@ -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.auth.service.AuthService;
|
||||||
import cn.meowrain.aioj.backend.framework.web.Results;
|
import cn.meowrain.aioj.backend.framework.web.Results;
|
||||||
import cn.meowrain.aioj.backend.framework.web.Result;
|
import cn.meowrain.aioj.backend.framework.web.Result;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
@RequestMapping("/v1/auth")
|
@RequestMapping("/v1/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
|
|
||||||
public AuthController(AuthService authService) {
|
|
||||||
this.authService = authService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public Result<UserLoginResponseDTO> login(@RequestBody UserLoginRequestDTO userLoginRequest) {
|
public Result<UserLoginResponseDTO> login(@RequestBody UserLoginRequestDTO userLoginRequest) {
|
||||||
UserLoginResponseDTO userLoginResponse = authService.userLogin(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")
|
@PostMapping("/auth")
|
||||||
public Result<String> auth(@RequestBody UserLoginRequestDTO userLoginRequest) {
|
public Result<String> auth(@RequestBody UserLoginRequestDTO userLoginRequest) {
|
||||||
UserLoginResponseDTO userLoginResponseDTO = authService.userLogin(userLoginRequest);
|
UserLoginResponseDTO userLoginResponseDTO = authService.userLogin(userLoginRequest);
|
||||||
return Results.success(userLoginResponseDTO.getToken());
|
return Results.success(userLoginResponseDTO.getAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,52 +22,9 @@ public class UserLoginResponseDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String unionId;
|
private String unionId;
|
||||||
|
|
||||||
/**
|
private String accessToken;
|
||||||
* 公众号openId
|
private String refreshToken;
|
||||||
*/
|
private Long expire;
|
||||||
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 static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,10 @@ public interface AuthService {
|
|||||||
*/
|
*/
|
||||||
UserLoginResponseDTO userLogin(UserLoginRequestDTO request);
|
UserLoginResponseDTO userLogin(UserLoginRequestDTO request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新token
|
||||||
|
* @param refreshToken
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
UserLoginResponseDTO refreshToken(String refreshToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package cn.meowrain.aioj.backend.auth.service.impl;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.crypto.digest.BCrypt;
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
import cn.meowrain.aioj.backend.auth.clients.UserClient;
|
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.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.chains.context.UserLoginRequestParamVerifyContext;
|
||||||
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
import cn.meowrain.aioj.backend.auth.dto.req.UserLoginRequestDTO;
|
||||||
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
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 cn.meowrain.aioj.backend.framework.web.Result;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -24,6 +29,8 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
private final JwtUtil jwtUtil;
|
private final JwtUtil jwtUtil;
|
||||||
private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext;
|
private final UserLoginRequestParamVerifyContext userLoginRequestParamVerifyContext;
|
||||||
private final UserClient userClient;
|
private final UserClient userClient;
|
||||||
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
|
private final JwtPropertiesConfiguration jwtPropertiesConfiguration;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) {
|
public UserLoginResponseDTO userLogin(UserLoginRequestDTO requestParam) {
|
||||||
@@ -43,16 +50,57 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
throw new ServiceException("用户不存在或者密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
throw new ServiceException("用户不存在或者密码错误", ErrorCode.NOT_LOGIN_ERROR);
|
||||||
}
|
}
|
||||||
// 生成 JWT
|
// 生成 JWT
|
||||||
String token = jwtUtil.generateToken(user);
|
String accessToken = jwtUtil.generateAccessToken(user);
|
||||||
|
String refreshToken = jwtUtil.generateRefreshToken(user.getId());
|
||||||
UserLoginResponseDTO resp = new UserLoginResponseDTO();
|
UserLoginResponseDTO resp = new UserLoginResponseDTO();
|
||||||
resp.setId(user.getId());
|
resp.setId(user.getId());
|
||||||
resp.setUserAccount(user.getUserAccount());
|
resp.setUserAccount(user.getUserAccount());
|
||||||
resp.setUserAvatar(user.getUserAvatar());
|
resp.setAccessToken(accessToken);
|
||||||
resp.setUserProfile(user.getUserProfile());
|
resp.setRefreshToken(refreshToken);
|
||||||
resp.setUserRole(user.getUserRole());
|
|
||||||
resp.setCreateTime(user.getCreateTime());
|
// refresh token存入到REDIS里面
|
||||||
resp.setUpdateTime(user.getUpdateTime());
|
stringRedisTemplate.opsForValue().set(
|
||||||
resp.setToken(token);
|
String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, user.getId()),
|
||||||
|
refreshToken,
|
||||||
|
jwtPropertiesConfiguration.getRefreshExpire(),
|
||||||
|
TimeUnit.MILLISECONDS);
|
||||||
return resp;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package cn.meowrain.aioj.backend.auth.utils;
|
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 cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
@@ -13,32 +13,47 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT工具类
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Component
|
@Component
|
||||||
public class JwtUtil {
|
public class JwtUtil {
|
||||||
|
|
||||||
private final JwtPropertiesConfiguration jwtConfig;
|
private final JwtPropertiesConfiguration jwtConfig;
|
||||||
|
|
||||||
private SecretKey getSigningKey() {
|
private SecretKey getSigningKey() {
|
||||||
return Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes());
|
return Keys.hmacShaKeyFor(jwtConfig.getSecret().getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateToken(UserAuthRespDTO user) {
|
/** 生成 Access Token */
|
||||||
|
public String generateAccessToken(UserAuthRespDTO user) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
Map<String, Object> claims = new HashMap<>();
|
||||||
claims.put("userId", user.getId());
|
claims.put("userId", user.getId());
|
||||||
|
claims.put("userName", user.getUserName());
|
||||||
claims.put("role", user.getUserRole());
|
claims.put("role", user.getUserRole());
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject(user.getUserAccount())
|
.subject(user.getUserAccount())
|
||||||
.issuedAt(new Date(now))
|
.issuedAt(new Date(now))
|
||||||
.expiration(new Date(now + jwtConfig.getExpire()))
|
.expiration(new Date(now + jwtConfig.getAccessExpire()))
|
||||||
.claims(claims)
|
.claims(claims)
|
||||||
.signWith(getSigningKey(), Jwts.SIG.HS256)
|
.signWith(getSigningKey(), Jwts.SIG.HS256)
|
||||||
.compact();
|
.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) {
|
public Claims parseClaims(String token) {
|
||||||
return Jwts.parser()
|
return Jwts.parser()
|
||||||
.verifyWith(getSigningKey())
|
.verifyWith(getSigningKey())
|
||||||
@@ -47,12 +62,12 @@ public class JwtUtil {
|
|||||||
.getPayload();
|
.getPayload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 校验 Token 是否过期 */
|
||||||
public boolean isTokenValid(String token) {
|
public boolean isTokenValid(String token) {
|
||||||
try {
|
try {
|
||||||
Claims claims = parseClaims(token);
|
Claims claims = parseClaims(token);
|
||||||
Date expiration = claims.getExpiration();
|
return claims.getExpiration().after(new Date());
|
||||||
return expiration != null && expiration.after(new Date());
|
} catch (Exception ignored) {
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
spring:
|
spring:
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: 10.0.0.10
|
||||||
|
port: 6379
|
||||||
|
password: 123456
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
|
|||||||
@@ -34,4 +34,5 @@ mybatis-plus:
|
|||||||
mapper-locations: classpath*:/mapper/**/*.xml
|
mapper-locations: classpath*:/mapper/**/*.xml
|
||||||
jwt:
|
jwt:
|
||||||
secret: "12345678901234567890123456789012" # 至少32字节!!
|
secret: "12345678901234567890123456789012" # 至少32字节!!
|
||||||
expire: 86400000 # 24小时(单位:毫秒)
|
access-expire: 900000 # 24小时
|
||||||
|
refresh-expire: 604800000 # 7天
|
||||||
@@ -28,5 +28,10 @@ public class UserController {
|
|||||||
return Results.success(userAuthDTO);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,9 @@ public interface UserService extends IService<User> {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
UserAuthRespDTO findAuthInfoByUserAccount(String userAccount);
|
UserAuthRespDTO findAuthInfoByUserAccount(String userAccount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户id查找用户认证信息
|
||||||
|
*/
|
||||||
|
UserAuthRespDTO findAuthInfoByUserId(String userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,4 +65,16 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
}
|
}
|
||||||
return null;
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user