This is an automated email from the ASF dual-hosted git repository.
wuzhiguo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/bigtop-manager.git
The following commit(s) were added to refs/heads/main by this push:
new 37c7e500 BIGTOP-4479: Add token version control (#251)
37c7e500 is described below
commit 37c7e500578be85c705032f3ba7e40271fc641d5
Author: ChunFuWu <[email protected]>
AuthorDate: Wed Aug 13 12:06:13 2025 +0800
BIGTOP-4479: Add token version control (#251)
---
.../bigtop/manager/common/constants/Caches.java | 19 +-
.../org/apache/bigtop/manager/dao/po/UserPO.java | 3 +
.../src/main/resources/mapper/mysql/UserMapper.xml | 2 +-
.../resources/mapper/postgresql/UserMapper.xml | 2 +-
.../manager/server/controller/LoginController.java | 5 +-
.../server/interceptor/AuthInterceptor.java | 36 ++-
.../bigtop/manager/server/model/vo/UserVO.java | 2 +
.../bigtop/manager/server/service/UserService.java | 8 +
.../server/service/impl/LoginServiceImpl.java | 16 +-
.../server/service/impl/UserServiceImpl.java | 22 +-
.../bigtop/manager/server/utils/CacheUtils.java | 293 +++++++++++++++++++--
.../bigtop/manager/server/utils/JWTUtils.java | 20 +-
.../src/main/resources/ddl/MySQL-DDL-CREATE.sql | 2 +
.../main/resources/ddl/PostgreSQL-DDL-CREATE.sql | 2 +
.../manager/server/service/LoginServiceTest.java | 7 +-
.../manager/server/service/UserServiceTest.java | 20 +-
.../bigtop/manager/server/utils/JWTUtilsTest.java | 13 +-
17 files changed, 404 insertions(+), 68 deletions(-)
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
b/bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/constants/Caches.java
similarity index 72%
copy from
bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
copy to
bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/constants/Caches.java
index 994f283a..a9b61858 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
+++
b/bigtop-manager-common/src/main/java/org/apache/bigtop/manager/common/constants/Caches.java
@@ -16,22 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.bigtop.manager.server.model.vo;
+package org.apache.bigtop.manager.common.constants;
-import lombok.Data;
+public class Caches {
-@Data
-public class UserVO {
+ public static final String CACHE_NONCE = "auth:nonce";
- private Long id;
+ public static final int NONCE_EXPIRE_TIME_MINUTES = 5;
- private String username;
+ public static final String CACHE_USER = "auth:user";
- private String nickname;
-
- private String createTime;
-
- private String updateTime;
-
- private Boolean status;
+ public static final int USER_EXPIRE_TIME_DAYS = 1;
}
diff --git
a/bigtop-manager-dao/src/main/java/org/apache/bigtop/manager/dao/po/UserPO.java
b/bigtop-manager-dao/src/main/java/org/apache/bigtop/manager/dao/po/UserPO.java
index e684ea70..650ffa74 100644
---
a/bigtop-manager-dao/src/main/java/org/apache/bigtop/manager/dao/po/UserPO.java
+++
b/bigtop-manager-dao/src/main/java/org/apache/bigtop/manager/dao/po/UserPO.java
@@ -46,4 +46,7 @@ public class UserPO extends BasePO implements Serializable {
@Column(name = "status")
private Boolean status;
+
+ @Column(name = "token_version")
+ private Integer tokenVersion;
}
diff --git a/bigtop-manager-dao/src/main/resources/mapper/mysql/UserMapper.xml
b/bigtop-manager-dao/src/main/resources/mapper/mysql/UserMapper.xml
index e05f9262..22d75539 100644
--- a/bigtop-manager-dao/src/main/resources/mapper/mysql/UserMapper.xml
+++ b/bigtop-manager-dao/src/main/resources/mapper/mysql/UserMapper.xml
@@ -24,7 +24,7 @@
<mapper namespace="org.apache.bigtop.manager.dao.repository.UserDao">
<sql id="baseColumns">
- id, username, password, nickname, status
+ id, username, password, nickname, status, token_version
</sql>
<select id="findByUsername"
resultType="org.apache.bigtop.manager.dao.po.UserPO">
diff --git
a/bigtop-manager-dao/src/main/resources/mapper/postgresql/UserMapper.xml
b/bigtop-manager-dao/src/main/resources/mapper/postgresql/UserMapper.xml
index 24a32e28..f1ccbc04 100644
--- a/bigtop-manager-dao/src/main/resources/mapper/postgresql/UserMapper.xml
+++ b/bigtop-manager-dao/src/main/resources/mapper/postgresql/UserMapper.xml
@@ -24,7 +24,7 @@
<mapper namespace="org.apache.bigtop.manager.dao.repository.UserDao">
<sql id="baseColumns">
- "id", "username", "password", "nickname", "status"
+ "id", "username", "password", "nickname", "status", "token_version"
</sql>
<select id="findByUsername"
resultType="org.apache.bigtop.manager.dao.po.UserPO">
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java
index 6b1a02c5..08be7024 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/controller/LoginController.java
@@ -18,6 +18,7 @@
*/
package org.apache.bigtop.manager.server.controller;
+import org.apache.bigtop.manager.common.constants.Caches;
import org.apache.bigtop.manager.server.annotations.Audit;
import org.apache.bigtop.manager.server.enums.ApiExceptionEnum;
import org.apache.bigtop.manager.server.exception.ApiException;
@@ -41,6 +42,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
+import java.util.concurrent.TimeUnit;
@Tag(name = "Login Controller")
@RestController
@@ -60,7 +62,8 @@ public class LoginController {
@GetMapping(value = "/nonce")
public ResponseEntity<String> nonce(String username) {
String nonce = PasswordUtils.randomString(16);
- CacheUtils.setCache(username, nonce);
+ String cacheKey = username + ":" + nonce;
+ CacheUtils.setCache(Caches.CACHE_NONCE, cacheKey, nonce,
Caches.NONCE_EXPIRE_TIME_MINUTES, TimeUnit.MINUTES);
return ResponseEntity.success(nonce);
}
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/AuthInterceptor.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/AuthInterceptor.java
index 1bbb2a83..a7cc2e84 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/AuthInterceptor.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/interceptor/AuthInterceptor.java
@@ -18,14 +18,19 @@
*/
package org.apache.bigtop.manager.server.interceptor;
+import org.apache.bigtop.manager.common.constants.Caches;
import org.apache.bigtop.manager.common.utils.JsonUtils;
import org.apache.bigtop.manager.server.enums.ApiExceptionEnum;
import org.apache.bigtop.manager.server.holder.SessionUserHolder;
+import org.apache.bigtop.manager.server.model.vo.UserVO;
+import org.apache.bigtop.manager.server.service.UserService;
+import org.apache.bigtop.manager.server.utils.CacheUtils;
import org.apache.bigtop.manager.server.utils.JWTUtils;
import org.apache.bigtop.manager.server.utils.ResponseEntity;
import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@@ -34,10 +39,15 @@ import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
@Component
public class AuthInterceptor implements HandlerInterceptor {
+ @Autowired
+ private UserService userService;
+
private ResponseEntity<?> responseEntity;
@Override
@@ -80,7 +90,31 @@ public class AuthInterceptor implements HandlerInterceptor {
try {
DecodedJWT decodedJWT = JWTUtils.resolveToken(token);
-
SessionUserHolder.setUserId(decodedJWT.getClaim(JWTUtils.CLAIM_ID).asLong());
+ Long userId = decodedJWT.getClaim(JWTUtils.CLAIM_ID).asLong();
+ Integer tokenVersion =
+ decodedJWT.getClaim(JWTUtils.CLAIM_TOKEN_VERSION).asInt();
+
+ // Check if the user exists
+ UserVO userVO = CacheUtils.getCache(Caches.CACHE_USER,
userId.toString(), UserVO.class);
+ if (userVO == null) {
+ userVO = userService.get(userId);
+ if (userVO == null) {
+ responseEntity =
ResponseEntity.error(ApiExceptionEnum.NEED_LOGIN);
+ return false;
+ }
+ // Explicitly set cache expiration time
+ CacheUtils.setCache(
+ Caches.CACHE_USER, userId.toString(), userVO,
Caches.USER_EXPIRE_TIME_DAYS, TimeUnit.DAYS);
+ }
+
+ // Check if the token version matches
+ if (!Objects.equals(tokenVersion, userVO.getTokenVersion())) {
+ CacheUtils.removeCache(Caches.CACHE_USER, userId.toString());
+ responseEntity =
ResponseEntity.error(ApiExceptionEnum.NEED_LOGIN);
+ return false;
+ }
+
+ SessionUserHolder.setUserId(userId);
} catch (Exception e) {
responseEntity = ResponseEntity.error(ApiExceptionEnum.NEED_LOGIN);
return false;
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
index 994f283a..e19aced4 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/model/vo/UserVO.java
@@ -34,4 +34,6 @@ public class UserVO {
private String updateTime;
private Boolean status;
+
+ private Integer tokenVersion;
}
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/UserService.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/UserService.java
index 421f6bd8..6924ed42 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/UserService.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/UserService.java
@@ -31,6 +31,14 @@ public interface UserService {
*/
UserVO current();
+ /**
+ * Get a user
+ *
+ * @param id user id
+ * @return user
+ */
+ UserVO get(Long id);
+
/**
* Update a user
*
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/LoginServiceImpl.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/LoginServiceImpl.java
index 2c1bbcf5..88d4b55f 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/LoginServiceImpl.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/LoginServiceImpl.java
@@ -18,12 +18,15 @@
*/
package org.apache.bigtop.manager.server.service.impl;
+import org.apache.bigtop.manager.common.constants.Caches;
import org.apache.bigtop.manager.dao.po.UserPO;
import org.apache.bigtop.manager.dao.repository.UserDao;
import org.apache.bigtop.manager.server.enums.ApiExceptionEnum;
import org.apache.bigtop.manager.server.exception.ApiException;
+import org.apache.bigtop.manager.server.model.converter.UserConverter;
import org.apache.bigtop.manager.server.model.dto.LoginDTO;
import org.apache.bigtop.manager.server.model.vo.LoginVO;
+import org.apache.bigtop.manager.server.model.vo.UserVO;
import org.apache.bigtop.manager.server.service.LoginService;
import org.apache.bigtop.manager.server.utils.CacheUtils;
import org.apache.bigtop.manager.server.utils.JWTUtils;
@@ -32,6 +35,7 @@ import org.apache.bigtop.manager.server.utils.PasswordUtils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
+import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@@ -54,7 +58,8 @@ public class LoginServiceImpl implements LoginService {
throw new ApiException(ApiExceptionEnum.USER_IS_DISABLED);
}
- String cache = CacheUtils.getCache(username);
+ String cacheKey = username + ":" + nonce;
+ String cache = CacheUtils.getCache(Caches.CACHE_NONCE, cacheKey,
String.class);
if (cache == null || !cache.equals(nonce)) {
throw new
ApiException(ApiExceptionEnum.INCORRECT_USERNAME_OR_PASSWORD);
}
@@ -63,9 +68,14 @@ public class LoginServiceImpl implements LoginService {
throw new
ApiException(ApiExceptionEnum.INCORRECT_USERNAME_OR_PASSWORD);
}
- CacheUtils.removeCache(username);
+ CacheUtils.removeCache(Caches.CACHE_NONCE, username);
- String token = JWTUtils.generateToken(user.getId(),
user.getUsername());
+ // After successful login, update the user cache to ensure that the
information in the cache is up-to-date
+ UserVO userVO = UserConverter.INSTANCE.fromPO2VO(user);
+ CacheUtils.setCache(
+ Caches.CACHE_USER, user.getId().toString(), userVO,
Caches.USER_EXPIRE_TIME_DAYS, TimeUnit.DAYS);
+
+ String token = JWTUtils.generateToken(user.getId(),
user.getUsername(), user.getTokenVersion());
LoginVO loginVO = new LoginVO();
loginVO.setToken(token);
return loginVO;
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/UserServiceImpl.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/UserServiceImpl.java
index 9631fd40..47bcb97e 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/UserServiceImpl.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/service/impl/UserServiceImpl.java
@@ -18,6 +18,7 @@
*/
package org.apache.bigtop.manager.server.service.impl;
+import org.apache.bigtop.manager.common.constants.Caches;
import org.apache.bigtop.manager.dao.po.UserPO;
import org.apache.bigtop.manager.dao.repository.UserDao;
import org.apache.bigtop.manager.server.enums.ApiExceptionEnum;
@@ -28,12 +29,14 @@ import
org.apache.bigtop.manager.server.model.dto.ChangePasswordDTO;
import org.apache.bigtop.manager.server.model.dto.UserDTO;
import org.apache.bigtop.manager.server.model.vo.UserVO;
import org.apache.bigtop.manager.server.service.UserService;
+import org.apache.bigtop.manager.server.utils.CacheUtils;
import org.apache.bigtop.manager.server.utils.PasswordUtils;
import org.apache.bigtop.manager.server.utils.Pbkdf2Utils;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
+import java.util.concurrent.TimeUnit;
@Service
public class UserServiceImpl implements UserService {
@@ -48,13 +51,23 @@ public class UserServiceImpl implements UserService {
return UserConverter.INSTANCE.fromPO2VO(userPO);
}
+ @Override
+ public UserVO get(Long id) {
+ UserPO userPO = userDao.findById(id);
+ return UserConverter.INSTANCE.fromPO2VO(userPO);
+ }
+
@Override
public UserVO update(UserDTO userDTO) {
Long id = SessionUserHolder.getUserId();
UserPO userPO = userDao.findOptionalById(id).orElseThrow(() -> new
ApiException(ApiExceptionEnum.NEED_LOGIN));
userPO.setNickname(userDTO.getNickname());
userDao.partialUpdateById(userPO);
- return UserConverter.INSTANCE.fromPO2VO(userPO);
+
+ // Update user information in cache
+ UserVO userVO = UserConverter.INSTANCE.fromPO2VO(userPO);
+ CacheUtils.setCache(Caches.CACHE_USER, id.toString(), userVO,
Caches.USER_EXPIRE_TIME_DAYS, TimeUnit.DAYS);
+ return userVO;
}
@Override
@@ -77,7 +90,12 @@ public class UserServiceImpl implements UserService {
String newPassword =
Pbkdf2Utils.getBcryptPassword(userPO.getUsername(),
changePasswordDTO.getNewPassword());
userPO.setPassword(newPassword);
+ userPO.setTokenVersion(userPO.getTokenVersion() + 1);
userDao.partialUpdateById(userPO);
- return UserConverter.INSTANCE.fromPO2VO(userPO);
+
+ // Update user information in cache
+ UserVO userVO = UserConverter.INSTANCE.fromPO2VO(userPO);
+ CacheUtils.setCache(Caches.CACHE_USER, id.toString(), userVO,
Caches.USER_EXPIRE_TIME_DAYS, TimeUnit.DAYS);
+ return userVO;
}
}
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/CacheUtils.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/CacheUtils.java
index 2aa4103a..25b9b251 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/CacheUtils.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/CacheUtils.java
@@ -22,72 +22,311 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheStats;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
- * Cache utility class
+ * Multi-cache utility class supporting different expiration times and
enhanced generic support
*/
public class CacheUtils {
/**
- * Create cache, expires in 5 minutes, maximum capacity 10000
+ * Default cache configuration
*/
- private static final Cache<String, String> CACHE =
CacheBuilder.newBuilder()
- .expireAfterWrite(5, TimeUnit.MINUTES)
- .maximumSize(10000)
- .build();
+ private static final int DEFAULT_EXPIRE_TIME = 5;
+
+ private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MINUTES;
+ private static final int DEFAULT_MAXIMUM_SIZE = 10000;
+
+ /**
+ * Multiple cache instances mapped by cache name
+ */
+ private static final ConcurrentHashMap<String, Cache<String, Object>>
CACHES = new ConcurrentHashMap<>();
+
+ /**
+ * Create or get cache with specific configuration
+ * Note: If a cache with the same name already exists, it will be returned
regardless
+ * of the provided configuration parameters.
+ *
+ * @param cacheName cache name/identifier
+ * @param expireTime expire time
+ * @param timeUnit time unit
+ * @param maximumSize maximum cache size
+ * @return cache instance
+ */
+ public static Cache<String, Object> getOrCreateCache(
+ String cacheName, int expireTime, TimeUnit timeUnit, int
maximumSize) {
+ if (StringUtils.isBlank(cacheName)) {
+ throw new IllegalArgumentException("Cache name cannot be blank");
+ }
+
+ return CACHES.computeIfAbsent(cacheName, name ->
CacheBuilder.newBuilder()
+ .expireAfterWrite(expireTime, timeUnit)
+ .maximumSize(maximumSize)
+ .recordStats()
+ .build());
+ }
+
+ /**
+ * Create or get cache with custom expire time (in minutes) and default
size
+ *
+ * @param cacheName cache name/identifier
+ * @param expireMinutes expire time in minutes
+ * @return cache instance
+ */
+ public static Cache<String, Object> getOrCreateCache(String cacheName, int
expireMinutes) {
+ return getOrCreateCache(cacheName, expireMinutes, DEFAULT_TIME_UNIT,
DEFAULT_MAXIMUM_SIZE);
+ }
+
+ /**
+ * Create or get cache with custom expire time and time unit, using
default size
+ *
+ * @param cacheName cache name/identifier
+ * @param expireTime expire time
+ * @param timeUnit time unit
+ * @return cache instance
+ */
+ public static Cache<String, Object> getOrCreateCache(String cacheName, int
expireTime, TimeUnit timeUnit) {
+ return getOrCreateCache(cacheName, expireTime, timeUnit,
DEFAULT_MAXIMUM_SIZE);
+ }
+
+ /**
+ * Get cache with default configuration
+ *
+ * @param cacheName cache name/identifier
+ * @return cache instance
+ */
+ public static Cache<String, Object> getOrCreateCache(String cacheName) {
+ return getOrCreateCache(cacheName, DEFAULT_EXPIRE_TIME,
DEFAULT_TIME_UNIT, DEFAULT_MAXIMUM_SIZE);
+ }
/**
- * Store cache
+ * Store cache value
*
- * @param key cache key
- * @param value cache value
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ * @param value cache value
*/
- public static void setCache(String key, String value) {
- if (StringUtils.isBlank(key) || value == null) {
+ public static void setCache(String cacheName, String key, Object value) {
+ if (StringUtils.isAnyBlank(cacheName, key) || value == null) {
return;
}
- CACHE.put(key, value);
+ getOrCreateCache(cacheName).put(key, value);
}
/**
- * Get cache
+ * Store cache value with specific cache configuration
*
- * @param key cache key
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ * @param value cache value
+ * @param expireTime expire time
+ */
+ public static void setCache(String cacheName, String key, Object value,
int expireTime) {
+ if (StringUtils.isAnyBlank(cacheName, key) || value == null) {
+ return;
+ }
+ getOrCreateCache(cacheName, expireTime).put(key, value);
+ }
+
+ /**
+ * Store cache value with specific cache configuration
+ *
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ * @param value cache value
+ * @param expireTime expire time
+ * @param timeUnit time unit
+ */
+ public static void setCache(String cacheName, String key, Object value,
int expireTime, TimeUnit timeUnit) {
+ if (StringUtils.isAnyBlank(cacheName, key) || value == null) {
+ return;
+ }
+ getOrCreateCache(cacheName, expireTime, timeUnit).put(key, value);
+ }
+
+ /**
+ * Store cache value with specific cache configuration
+ *
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ * @param value cache value
+ * @param expireTime expire time
+ * @param timeUnit time unit
+ * @param maximumSize maximum cache size
+ */
+ public static void setCache(
+ String cacheName, String key, Object value, int expireTime,
TimeUnit timeUnit, int maximumSize) {
+ if (StringUtils.isAnyBlank(cacheName, key) || value == null) {
+ return;
+ }
+ getOrCreateCache(cacheName, expireTime, timeUnit,
maximumSize).put(key, value);
+ }
+
+ /**
+ * Batch set cache values
+ *
+ * @param cacheName cache name/identifier
+ * @param values map of key-value pairs to cache
+ */
+ public static void setCacheBatch(String cacheName, Map<String, Object>
values) {
+ if (StringUtils.isBlank(cacheName) || values == null ||
values.isEmpty()) {
+ return;
+ }
+ Cache<String, Object> cache = getOrCreateCache(cacheName);
+ cache.putAll(values);
+ }
+
+ /**
+ * Get cache value with type safety
+ *
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ * @param clazz expected class type
+ * @param <T> generic type
+ * @return cache value of specified type (returns null if not found,
expired or type mismatch)
+ */
+ public static <T> T getCache(String cacheName, String key, Class<T> clazz)
{
+ if (StringUtils.isAnyBlank(cacheName, key) || clazz == null) {
+ return null;
+ }
+
+ Object value = getCache(cacheName, key);
+ if (value != null && clazz.isInstance(value)) {
+ return clazz.cast(value);
+ }
+ return null;
+ }
+
+ /**
+ * Generic version of getCache method
+ *
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ * @param <T> generic type
* @return cache value (returns null if not found or expired)
*/
- public static String getCache(String key) {
- if (StringUtils.isBlank(key)) {
+ @SuppressWarnings("unchecked")
+ public static <T> T getCache(String cacheName, String key) {
+ if (StringUtils.isAnyBlank(cacheName, key)) {
return null;
}
- return CACHE.getIfPresent(key);
+ Cache<String, Object> cache = CACHES.get(cacheName);
+ return cache != null ? (T) cache.getIfPresent(key) : null;
+ }
+
+ /**
+ * Check if cache with given name exists
+ *
+ * @param cacheName cache name/identifier
+ * @return true if cache exists, false otherwise
+ */
+ public static boolean exists(String cacheName) {
+ return StringUtils.isNotBlank(cacheName) &&
CACHES.containsKey(cacheName);
+ }
+
+ /**
+ * Remove specified cache entry
+ *
+ * @param cacheName cache name/identifier
+ * @param key cache key
+ */
+ public static void removeCache(String cacheName, String key) {
+ if (StringUtils.isAnyBlank(cacheName, key)) {
+ return;
+ }
+ Cache<String, Object> cache = CACHES.get(cacheName);
+ if (cache != null) {
+ cache.invalidate(key);
+ }
}
/**
- * Remove specified cache
+ * Clear all entries in specific cache
*
- * @param key cache key
+ * @param cacheName cache name/identifier
*/
- public static void removeCache(String key) {
- if (StringUtils.isBlank(key)) {
+ public static void clearCache(String cacheName) {
+ if (StringUtils.isBlank(cacheName)) {
return;
}
- CACHE.invalidate(key);
+ Cache<String, Object> cache = CACHES.get(cacheName);
+ if (cache != null) {
+ cache.invalidateAll();
+ cache.cleanUp();
+ }
}
/**
* Clear all caches
*/
public static void clearAll() {
- CACHE.invalidateAll();
- CACHE.cleanUp();
+ CACHES.values().forEach(cache -> {
+ cache.invalidateAll();
+ cache.cleanUp();
+ });
+ CACHES.clear();
}
/**
- * Get cache size
+ * Get cache size for specific cache
+ *
+ * @param cacheName cache name/identifier
+ * @return cache size
*/
- public static long size() {
- return CACHE.size();
+ public static long size(String cacheName) {
+ if (StringUtils.isBlank(cacheName)) {
+ return 0;
+ }
+ Cache<String, Object> cache = CACHES.get(cacheName);
+ return cache != null ? cache.size() : 0;
+ }
+
+ /**
+ * Get total size of all caches
+ *
+ * @return total cache size
+ */
+ public static long totalSize() {
+ return CACHES.values().stream().mapToLong(Cache::size).sum();
+ }
+
+ /**
+ * Get cache statistics
+ *
+ * @param cacheName cache name/identifier
+ * @return cache statistics
+ */
+ public static CacheStats getStats(String cacheName) {
+ if (StringUtils.isBlank(cacheName)) {
+ return null;
+ }
+ Cache<String, Object> cache = CACHES.get(cacheName);
+ return cache != null ? cache.stats() : null;
+ }
+
+ /**
+ * Force cleanup of expired entries in all caches
+ */
+ public static void cleanupAll() {
+ CACHES.values().forEach(Cache::cleanUp);
+ }
+
+ /**
+ * Force cleanup of expired entries in specific cache
+ *
+ * @param cacheName cache name/identifier
+ */
+ public static void cleanup(String cacheName) {
+ if (StringUtils.isBlank(cacheName)) {
+ return;
+ }
+ Cache<String, Object> cache = CACHES.get(cacheName);
+ if (cache != null) {
+ cache.cleanUp();
+ }
}
}
diff --git
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/JWTUtils.java
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/JWTUtils.java
index 02ca8c63..a0f7e261 100644
---
a/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/JWTUtils.java
+++
b/bigtop-manager-server/src/main/java/org/apache/bigtop/manager/server/utils/JWTUtils.java
@@ -22,8 +22,8 @@ import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
-import java.util.Calendar;
-import java.util.Date;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
public class JWTUtils {
@@ -31,17 +31,21 @@ public class JWTUtils {
public static final String CLAIM_USERNAME = "username";
+ public static final String CLAIM_TOKEN_VERSION = "token_version";
+
protected static final String SIGN = "r0PGVyvjKOxUBwGt";
- public static String generateToken(Long id, String username) {
- Calendar calendar = Calendar.getInstance();
- calendar.add(Calendar.DAY_OF_MONTH, 7);
- Date date = calendar.getTime();
+ // Token validity period (days)
+ private static final int TOKEN_EXPIRATION_DAYS = 7;
+
+ public static String generateToken(Long userId, String username, Integer
tokenVersion) {
+ Instant expireTime = Instant.now().plus(TOKEN_EXPIRATION_DAYS,
ChronoUnit.DAYS);
return JWT.create()
- .withClaim(CLAIM_ID, id)
+ .withClaim(CLAIM_ID, userId)
.withClaim(CLAIM_USERNAME, username)
- .withExpiresAt(date)
+ .withClaim(CLAIM_TOKEN_VERSION, tokenVersion)
+ .withExpiresAt(expireTime)
.sign(Algorithm.HMAC256(SIGN));
}
diff --git a/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql
b/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql
index e9118535..aeab6931 100644
--- a/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql
+++ b/bigtop-manager-server/src/main/resources/ddl/MySQL-DDL-CREATE.sql
@@ -338,6 +338,8 @@ CREATE TABLE `llm_chat_message`
INSERT INTO user (username, password, nickname, status)
VALUES ('admin',
'$2b$10$bdTvADKA0dSJYT3wMU3LFeIEnxzKQHeWN3XcHJ5jQpsIo7ju1U5Yi',
'Administrator', true);
+ALTER TABLE user ADD COLUMN token_version INTEGER DEFAULT 1;
+
INSERT INTO repo (name, arch, base_url, pkg_name, checksum, type)
VALUES
('general', 'x86_64,aarch64', 'http://your-repo/', null, null, 1),
diff --git
a/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql
b/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql
index 71001cf4..35feb63f 100644
--- a/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql
+++ b/bigtop-manager-server/src/main/resources/ddl/PostgreSQL-DDL-CREATE.sql
@@ -351,6 +351,8 @@ CREATE INDEX idx_message_user_id ON llm_chat_message
(user_id);
INSERT INTO "user" (username, password, nickname, status)
VALUES ('admin',
'$2b$10$bdTvADKA0dSJYT3wMU3LFeIEnxzKQHeWN3XcHJ5jQpsIo7ju1U5Yi',
'Administrator', true);
+ALTER TABLE "user" ADD token_version INT DEFAULT 1;
+
INSERT INTO repo (name, arch, base_url, pkg_name, checksum, type)
VALUES
('general', 'x86_64,aarch64', 'http://your-repo/', null, null, 1),
diff --git
a/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/LoginServiceTest.java
b/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/LoginServiceTest.java
index e5430717..86c06db1 100644
---
a/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/LoginServiceTest.java
+++
b/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/LoginServiceTest.java
@@ -19,6 +19,7 @@
package org.apache.bigtop.manager.server.service;
+import org.apache.bigtop.manager.common.constants.Caches;
import org.apache.bigtop.manager.dao.po.UserPO;
import org.apache.bigtop.manager.dao.repository.UserDao;
import org.apache.bigtop.manager.server.enums.ApiExceptionEnum;
@@ -36,6 +37,8 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.concurrent.TimeUnit;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -60,9 +63,11 @@ public class LoginServiceTest {
@BeforeEach
public void setUp() {
nonce = PasswordUtils.randomString(16);
- CacheUtils.setCache(USERNAME, nonce);
+ String cacheKey = USERNAME + ":" + nonce;
+ CacheUtils.setCache(Caches.CACHE_NONCE, cacheKey, nonce,
Caches.NONCE_EXPIRE_TIME_MINUTES, TimeUnit.MINUTES);
mockUser = new UserPO();
+ mockUser.setId(1L);
mockUser.setUsername(USERNAME);
mockUser.setPassword(Pbkdf2Utils.getBcryptPassword(USERNAME,
RAW_PASSWORD));
mockUser.setStatus(true);
diff --git
a/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/UserServiceTest.java
b/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/UserServiceTest.java
index c3b17e77..1b9b150c 100644
---
a/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/UserServiceTest.java
+++
b/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/service/UserServiceTest.java
@@ -23,6 +23,7 @@ import org.apache.bigtop.manager.dao.po.UserPO;
import org.apache.bigtop.manager.dao.repository.UserDao;
import org.apache.bigtop.manager.server.enums.ApiExceptionEnum;
import org.apache.bigtop.manager.server.exception.ApiException;
+import org.apache.bigtop.manager.server.holder.SessionUserHolder;
import org.apache.bigtop.manager.server.model.dto.UserDTO;
import org.apache.bigtop.manager.server.service.impl.UserServiceImpl;
@@ -30,6 +31,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
+import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
@@ -37,6 +39,7 @@ import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@@ -50,12 +53,19 @@ public class UserServiceTest {
@Test
public void testCurrentAndUpdate() {
- when(userDao.findOptionalById(any())).thenReturn(Optional.of(new
UserPO()));
- assert userService.current() != null;
+ // Behavior of Mock SessionUserHolder
+ try (MockedStatic<SessionUserHolder> mockedSessionHolder =
mockStatic(SessionUserHolder.class)) {
- UserDTO userDTO = new UserDTO();
- userDTO.setNickname("test");
- assert userService.update(userDTO).getNickname().equals("test");
+ // Set SessionUserHolder. getUserId() to return 1L
+
mockedSessionHolder.when(SessionUserHolder::getUserId).thenReturn(1L);
+
+ when(userDao.findOptionalById(1L)).thenReturn(Optional.of(new
UserPO()));
+ assert userService.current() != null;
+
+ UserDTO userDTO = new UserDTO();
+ userDTO.setNickname("test");
+ assert userService.update(userDTO).getNickname().equals("test");
+ }
}
@Test
diff --git
a/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/utils/JWTUtilsTest.java
b/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/utils/JWTUtilsTest.java
index 53152e57..e9316093 100644
---
a/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/utils/JWTUtilsTest.java
+++
b/bigtop-manager-server/src/test/java/org/apache/bigtop/manager/server/utils/JWTUtilsTest.java
@@ -39,12 +39,15 @@ public class JWTUtilsTest {
public void testGenerateTokenNormal() {
Long id = 1L;
String username = "testUser";
- String token = JWTUtils.generateToken(id, username);
+ Integer tokenVersion = 1;
+ String token = JWTUtils.generateToken(id, username, tokenVersion);
assertNotNull(token);
DecodedJWT decodedJWT = JWTUtils.resolveToken(token);
- assertEquals(decodedJWT.getClaim(JWTUtils.CLAIM_ID).asLong(), id);
- assertEquals(decodedJWT.getClaim(JWTUtils.CLAIM_USERNAME).asString(),
username);
+ assertEquals(id, decodedJWT.getClaim(JWTUtils.CLAIM_ID).asLong());
+ assertEquals(username,
decodedJWT.getClaim(JWTUtils.CLAIM_USERNAME).asString());
+ assertEquals(
+ tokenVersion,
decodedJWT.getClaim(JWTUtils.CLAIM_TOKEN_VERSION).asInt());
}
@Test
@@ -79,10 +82,10 @@ public class JWTUtilsTest {
@Test
public void testGenerateTokenUsernameEmpty() {
- String token = JWTUtils.generateToken(1L, "");
+ String token = JWTUtils.generateToken(1L, "", 1);
assertNotNull(token);
DecodedJWT decodedJWT = JWTUtils.resolveToken(token);
- assertEquals(decodedJWT.getClaim(JWTUtils.CLAIM_USERNAME).asString(),
"");
+ assertEquals("",
decodedJWT.getClaim(JWTUtils.CLAIM_USERNAME).asString());
}
}