登录接口
This commit is contained in:
@@ -6,13 +6,10 @@ import jakarta.annotation.Resource;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
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 org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
import static com.bicloud.common.utils.RedisConstants.JWT_BLACKLIST_KEY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT认证拦截器
|
* JWT认证拦截器
|
||||||
*/
|
*/
|
||||||
@@ -23,9 +20,6 @@ public class JwtAuthInterceptor implements HandlerInterceptor {
|
|||||||
@Resource
|
@Resource
|
||||||
private JwtUtils jwtUtils;
|
private JwtUtils jwtUtils;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
// 1. 从请求头获取Token
|
// 1. 从请求头获取Token
|
||||||
@@ -47,17 +41,7 @@ public class JwtAuthInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 检查Token是否在黑名单中
|
// 4. 解析Token,获取用户信息
|
||||||
String blacklistKey = JWT_BLACKLIST_KEY + token;
|
|
||||||
Boolean isBlacklisted = stringRedisTemplate.hasKey(blacklistKey);
|
|
||||||
if (Boolean.TRUE.equals(isBlacklisted)) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("{\"code\":401,\"message\":\"Token已失效,请重新登录!\"}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 解析Token,获取用户信息
|
|
||||||
Long userId = jwtUtils.getUserIdFromToken(token);
|
Long userId = jwtUtils.getUserIdFromToken(token);
|
||||||
String username = jwtUtils.getUsernameFromToken(token);
|
String username = jwtUtils.getUsernameFromToken(token);
|
||||||
|
|
||||||
@@ -68,7 +52,7 @@ public class JwtAuthInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. 将用户信息存入ThreadLocal
|
// 5. 将用户信息存入ThreadLocal
|
||||||
UserContext.setUserId(userId);
|
UserContext.setUserId(userId);
|
||||||
UserContext.setUsername(username);
|
UserContext.setUsername(username);
|
||||||
|
|
||||||
|
|||||||
@@ -36,27 +36,11 @@ public class JwtUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成AccessToken
|
* 生成Token
|
||||||
*/
|
*/
|
||||||
public String generateAccessToken(Long userId, String username) {
|
public String generateToken(Long userId, String username) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date expiryDate = new Date(now.getTime() + jwtProperties.getAccessTokenExpiration() * 1000);
|
Date expiryDate = new Date(now.getTime() + jwtProperties.getTokenExpiration() * 1000);
|
||||||
|
|
||||||
return Jwts.builder()
|
|
||||||
.subject(String.valueOf(userId))
|
|
||||||
.claim("username", username)
|
|
||||||
.issuedAt(now)
|
|
||||||
.expiration(expiryDate)
|
|
||||||
.signWith(secretKey)
|
|
||||||
.compact();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成RefreshToken
|
|
||||||
*/
|
|
||||||
public String generateRefreshToken(Long userId, String username) {
|
|
||||||
Date now = new Date();
|
|
||||||
Date expiryDate = new Date(now.getTime() + jwtProperties.getRefreshTokenExpiration() * 1000);
|
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject(String.valueOf(userId))
|
.subject(String.valueOf(userId))
|
||||||
@@ -127,24 +111,11 @@ public class JwtUtils {
|
|||||||
* 从请求头中提取Token
|
* 从请求头中提取Token
|
||||||
*/
|
*/
|
||||||
public String extractToken(HttpServletRequest request) {
|
public String extractToken(HttpServletRequest request) {
|
||||||
String bearerToken = request.getHeader(jwtProperties.getTokenHeader());
|
String token = request.getHeader(jwtProperties.getTokenHeader());
|
||||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtProperties.getTokenPrefix())) {
|
if (StringUtils.hasText(token)) {
|
||||||
return bearerToken.substring(jwtProperties.getTokenPrefix().length());
|
return token;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取Token剩余有效时间(秒)
|
|
||||||
*/
|
|
||||||
public Long getTokenRemainingTime(String token) {
|
|
||||||
Claims claims = parseToken(token);
|
|
||||||
if (claims == null) {
|
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
Date expiration = claims.getExpiration();
|
|
||||||
long remainingTime = (expiration.getTime() - System.currentTimeMillis()) / 1000;
|
|
||||||
return Math.max(remainingTime, 0L);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,22 +18,12 @@ public class JwtProperties {
|
|||||||
private String secret;
|
private String secret;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AccessToken过期时间(秒)
|
* Token过期时间(秒)
|
||||||
*/
|
*/
|
||||||
private Long accessTokenExpiration;
|
private Long tokenExpiration;
|
||||||
|
|
||||||
/**
|
|
||||||
* RefreshToken过期时间(秒)
|
|
||||||
*/
|
|
||||||
private Long refreshTokenExpiration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token请求头名称
|
* Token请求头名称
|
||||||
*/
|
*/
|
||||||
private String tokenHeader;
|
private String tokenHeader;
|
||||||
|
|
||||||
/**
|
|
||||||
* Token前缀
|
|
||||||
*/
|
|
||||||
private String tokenPrefix;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.bicloud.config;
|
package com.bicloud.config;
|
||||||
|
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
import io.swagger.v3.oas.models.info.License;
|
import io.swagger.v3.oas.models.info.License;
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
// 用户相关放行路径
|
// 用户相关放行路径
|
||||||
"/user/code",
|
"/user/code",
|
||||||
"/user/register",
|
"/user/register",
|
||||||
"/user/login",
|
"/user/login"
|
||||||
"/user/refresh-token"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,16 +58,6 @@ public class UserController {
|
|||||||
return Result.success(loginVo);
|
return Result.success(loginVo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新Token
|
|
||||||
*/
|
|
||||||
@PostMapping("/refresh-token")
|
|
||||||
@Operation(summary = "刷新Token")
|
|
||||||
public Result<String> refreshToken(@RequestParam("refreshToken") String refreshToken) {
|
|
||||||
String newAccessToken = userService.refreshToken(refreshToken);
|
|
||||||
return Result.success(newAccessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户登出
|
* 用户登出
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,15 +17,9 @@ import lombok.NoArgsConstructor;
|
|||||||
public class LoginVo {
|
public class LoginVo {
|
||||||
|
|
||||||
@Schema(description = "访问令牌")
|
@Schema(description = "访问令牌")
|
||||||
private String accessToken;
|
private String token;
|
||||||
|
|
||||||
@Schema(description = "刷新令牌")
|
@Schema(description = "令牌过期时间(秒)", example = "86400")
|
||||||
private String refreshToken;
|
|
||||||
|
|
||||||
@Schema(description = "令牌类型", example = "Bearer")
|
|
||||||
private String tokenType;
|
|
||||||
|
|
||||||
@Schema(description = "访问令牌过期时间(秒)", example = "7200")
|
|
||||||
private Long expiresIn;
|
private Long expiresIn;
|
||||||
|
|
||||||
@Schema(description = "用户ID")
|
@Schema(description = "用户ID")
|
||||||
|
|||||||
@@ -18,11 +18,6 @@ public interface UserService {
|
|||||||
*/
|
*/
|
||||||
LoginVo login(LoginDto loginDto);
|
LoginVo login(LoginDto loginDto);
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新Token
|
|
||||||
*/
|
|
||||||
String refreshToken(String refreshToken);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户登出
|
* 用户登出
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -189,61 +189,21 @@ public class UserServiceImpl implements UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. 生成Token
|
// 4. 生成Token
|
||||||
String accessToken = jwtUtils.generateAccessToken(user.getId(), user.getUsername());
|
String token = jwtUtils.generateToken(user.getId(), user.getUsername());
|
||||||
String refreshToken = jwtUtils.generateRefreshToken(user.getId(), user.getUsername());
|
|
||||||
|
|
||||||
// 5. 构建返回结果
|
// 5. 构建返回结果
|
||||||
return LoginVo.builder()
|
return LoginVo.builder()
|
||||||
.accessToken(accessToken)
|
.token(token)
|
||||||
.refreshToken(refreshToken)
|
.expiresIn(jwtProperties.getTokenExpiration())
|
||||||
.tokenType("Bearer")
|
|
||||||
.expiresIn(jwtProperties.getAccessTokenExpiration())
|
|
||||||
.userId(user.getId())
|
.userId(user.getId())
|
||||||
.username(user.getUsername())
|
.username(user.getUsername())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String refreshToken(String refreshToken) {
|
|
||||||
// 1. 验证RefreshToken
|
|
||||||
if (!jwtUtils.validateToken(refreshToken)) {
|
|
||||||
throw new BusinessException("RefreshToken无效或已过期!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 检查黑名单
|
|
||||||
String blacklistKey = JWT_BLACKLIST_KEY + refreshToken;
|
|
||||||
Boolean isBlacklisted = stringRedisTemplate.hasKey(blacklistKey);
|
|
||||||
if (Boolean.TRUE.equals(isBlacklisted)) {
|
|
||||||
throw new BusinessException("Token已失效,请重新登录!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 从Token中获取用户信息
|
|
||||||
Long userId = jwtUtils.getUserIdFromToken(refreshToken);
|
|
||||||
String username = jwtUtils.getUsernameFromToken(refreshToken);
|
|
||||||
|
|
||||||
if (userId == null || username == null) {
|
|
||||||
throw new BusinessException("Token解析失败!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 生成新的AccessToken
|
|
||||||
return jwtUtils.generateAccessToken(userId, username);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void logout(String token) {
|
public void logout(String token) {
|
||||||
// 1. 将Token加入黑名单
|
// 简化版:不使用黑名单,由前端删除token即可
|
||||||
String blacklistKey = JWT_BLACKLIST_KEY + token;
|
log.info("用户登出成功");
|
||||||
Long remainingTime = jwtUtils.getTokenRemainingTime(token);
|
|
||||||
|
|
||||||
// 2. 设置黑名单过期时间为Token剩余有效时间
|
|
||||||
stringRedisTemplate.opsForValue().set(
|
|
||||||
blacklistKey,
|
|
||||||
"1",
|
|
||||||
remainingTime,
|
|
||||||
TimeUnit.SECONDS
|
|
||||||
);
|
|
||||||
|
|
||||||
log.info("用户登出成功,Token已加入黑名单");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -55,7 +55,5 @@ logging:
|
|||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: bicloud-jwt-secret-key-2024-spring-boot-application-secure-token-generation
|
secret: bicloud-jwt-secret-key-2024-spring-boot-application-secure-token-generation
|
||||||
access-token-expiration: 7200
|
token-expiration: 86400
|
||||||
refresh-token-expiration: 604800
|
|
||||||
token-header: Authorization
|
token-header: Authorization
|
||||||
token-prefix: "Bearer "
|
|
||||||
|
|||||||
Reference in New Issue
Block a user