fix(auth): 增加 JWT 安全版本号与 TokenManager.invalidateUserSessions,统一角色与密码变更的按用户下线行为
Closes #ID8B31
This commit is contained in:
@@ -35,4 +35,9 @@ public interface JwtClaimConstants {
|
|||||||
*/
|
*/
|
||||||
String AUTHORITIES = "authorities";
|
String AUTHORITIES = "authorities";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全版本号,用于按用户失效历史令牌
|
||||||
|
*/
|
||||||
|
String SECURITY_VERSION = "securityVersion";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ public interface RedisConstants {
|
|||||||
String USER_REFRESH_TOKEN = "auth:user:refresh:{}";
|
String USER_REFRESH_TOKEN = "auth:user:refresh:{}";
|
||||||
// 黑名单 Token(用于退出登录或注销)
|
// 黑名单 Token(用于退出登录或注销)
|
||||||
String BLACKLIST_TOKEN = "auth:token:blacklist:{}";
|
String BLACKLIST_TOKEN = "auth:token:blacklist:{}";
|
||||||
|
// 用户安全版本号(用于按用户失效历史 JWT)
|
||||||
|
String USER_SECURITY_VERSION = "auth:user:security_version:{}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ public class JwtTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean validateToken(String token) {
|
public boolean validateToken(String token) {
|
||||||
return validateToken(token,false);
|
return validateToken(token, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +121,7 @@ public class JwtTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean validateRefreshToken(String refreshToken) {
|
public boolean validateRefreshToken(String refreshToken) {
|
||||||
return validateToken(refreshToken,true);
|
return validateToken(refreshToken, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,17 +138,34 @@ public class JwtTokenManager implements TokenManager {
|
|||||||
boolean isValid = jwt.setKey(secretKey).validate(0);
|
boolean isValid = jwt.setKey(secretKey).validate(0);
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
// 检查 Token 是否已被加入黑名单(注销、修改密码等场景)
|
|
||||||
JSONObject payloads = jwt.getPayloads();
|
JSONObject payloads = jwt.getPayloads();
|
||||||
|
// 1. 校验刷新令牌类型(仅在校验刷新令牌场景启用)
|
||||||
String jti = payloads.getStr(JWTPayload.JWT_ID);
|
String jti = payloads.getStr(JWTPayload.JWT_ID);
|
||||||
if(validateRefreshToken) {
|
if (validateRefreshToken) {
|
||||||
//刷新token需要校验token类别
|
//刷新token需要校验token类别
|
||||||
boolean isRefreshToken = payloads.getBool(JwtClaimConstants.TOKEN_TYPE);
|
boolean isRefreshToken = payloads.getBool(JwtClaimConstants.TOKEN_TYPE);
|
||||||
if (!isRefreshToken) {
|
if (!isRefreshToken) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 判断是否在黑名单中,如果在,则返回 false 标识Token无效
|
// 2. 校验安全版本号(用于按用户维度失效历史 Token)
|
||||||
|
Long userId = payloads.getLong(JwtClaimConstants.USER_ID);
|
||||||
|
if (userId != null) {
|
||||||
|
// 老版本 Token 可能没有 SECURITY_VERSION 声明,视为 0 版本
|
||||||
|
Integer tokenVersionRaw = payloads.getInt(JwtClaimConstants.SECURITY_VERSION);
|
||||||
|
int tokenVersion = tokenVersionRaw != null ? tokenVersionRaw : 0;
|
||||||
|
|
||||||
|
String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userId);
|
||||||
|
Integer currentVersionRaw = (Integer) redisTemplate.opsForValue().get(versionKey);
|
||||||
|
int currentVersion = currentVersionRaw != null ? currentVersionRaw : 0;
|
||||||
|
|
||||||
|
// 如果当前版本号比 Token 携带的版本号新,则认为该 Token 已失效
|
||||||
|
if (tokenVersion < currentVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 判断是否在黑名单中,如果在,则返回 false 标识Token无效
|
||||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) {
|
if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -167,7 +184,7 @@ public class JwtTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void invalidateToken(String token) {
|
public void invalidateToken(String token) {
|
||||||
if(StringUtils.isBlank(token)) {
|
if (StringUtils.isBlank(token)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,16 +203,33 @@ public class JwtTokenManager implements TokenManager {
|
|||||||
// Token已过期,直接返回
|
// Token已过期,直接返回
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 计算Token剩余时间,将其加入黑名单
|
// 计算Token剩余时间,将其加入黑名单(值使用简单布尔标记即可)
|
||||||
int expirationIn = expirationAt - currentTimeSeconds;
|
int expirationIn = expirationAt - currentTimeSeconds;
|
||||||
redisTemplate.opsForValue().set(blacklistTokenKey, null, expirationIn, TimeUnit.SECONDS);
|
redisTemplate.opsForValue().set(blacklistTokenKey, Boolean.TRUE, expirationIn, TimeUnit.SECONDS);
|
||||||
} else {
|
} else {
|
||||||
// 永不过期的Token永久加入黑名单
|
// 永不过期的Token永久加入黑名单
|
||||||
redisTemplate.opsForValue().set(blacklistTokenKey, null);
|
redisTemplate.opsForValue().set(blacklistTokenKey, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失效指定用户的所有会话
|
||||||
|
* <p>
|
||||||
|
* 通过提升用户的安全版本号,使携带旧版本号的 Token 在后续校验时全部失效
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void invalidateUserSessions(Long userId) {
|
||||||
|
if (userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userId);
|
||||||
|
// 递增版本号
|
||||||
|
redisTemplate.opsForValue().increment(versionKey);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新令牌
|
* 刷新令牌
|
||||||
*
|
*
|
||||||
@@ -259,6 +293,12 @@ public class JwtTokenManager implements TokenManager {
|
|||||||
payload.put(JwtClaimConstants.TOKEN_TYPE, true);
|
payload.put(JwtClaimConstants.TOKEN_TYPE, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置安全版本号:不存在时默认为 0
|
||||||
|
String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userDetails.getUserId());
|
||||||
|
Integer currentVersion = (Integer) redisTemplate.opsForValue().get(versionKey);
|
||||||
|
int securityVersion = currentVersion != null ? currentVersion : 0;
|
||||||
|
payload.put(JwtClaimConstants.SECURITY_VERSION, securityVersion);
|
||||||
|
|
||||||
// 设置过期时间 -1 表示永不过期
|
// 设置过期时间 -1 表示永不过期
|
||||||
if (ttl != -1) {
|
if (ttl != -1) {
|
||||||
Date expiresAt = DateUtil.offsetSecond(now, ttl);
|
Date expiresAt = DateUtil.offsetSecond(now, ttl);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.springframework.security.core.GrantedAuthority;
|
|||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -134,17 +135,16 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationToken refreshToken(String refreshToken) {
|
public AuthenticationToken refreshToken(String refreshToken) {
|
||||||
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken));
|
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue()
|
||||||
|
.get(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken));
|
||||||
if (onlineUser == null) {
|
if (onlineUser == null) {
|
||||||
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
|
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
|
||||||
}
|
}
|
||||||
|
Object oldAccessTokenValue = redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, onlineUser.getUserId()));
|
||||||
String oldAccessToken = (String) redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, onlineUser.getUserId()));
|
|
||||||
|
|
||||||
// 删除旧的访问令牌记录
|
// 删除旧的访问令牌记录
|
||||||
if (oldAccessToken != null) {
|
Optional.of(oldAccessTokenValue)
|
||||||
redisTemplate.delete(formatTokenKey(oldAccessToken));
|
.map(String.class::cast)
|
||||||
}
|
.ifPresent(oldAccessToken -> redisTemplate.delete(formatTokenKey(oldAccessToken)));
|
||||||
|
|
||||||
// 生成新访问令牌并存储
|
// 生成新访问令牌并存储
|
||||||
String newAccessToken = IdUtil.fastSimpleUUID();
|
String newAccessToken = IdUtil.fastSimpleUUID();
|
||||||
@@ -168,24 +168,42 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue().get(formatTokenKey(token));
|
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue().get(formatTokenKey(token));
|
||||||
if (onlineUser != null) {
|
if (onlineUser != null) {
|
||||||
Long userId = onlineUser.getUserId();
|
Long userId = onlineUser.getUserId();
|
||||||
// 1. 删除访问令牌相关
|
invalidateUserSessions(userId);
|
||||||
String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userId);
|
|
||||||
String accessToken = (String) redisTemplate.opsForValue().get(userAccessKey);
|
|
||||||
if (accessToken != null) {
|
|
||||||
redisTemplate.delete(formatTokenKey(accessToken));
|
|
||||||
redisTemplate.delete(userAccessKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 删除刷新令牌相关
|
|
||||||
String userRefreshKey = StrUtil.format(RedisConstants.Auth.USER_REFRESH_TOKEN, userId);
|
|
||||||
String refreshToken = (String) redisTemplate.opsForValue().get(userRefreshKey);
|
|
||||||
if (refreshToken != null) {
|
|
||||||
redisTemplate.delete(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken));
|
|
||||||
redisTemplate.delete(userRefreshKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使指定用户的所有会话失效
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void invalidateUserSessions(Long userId) {
|
||||||
|
if (userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 删除访问令牌相关
|
||||||
|
String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userId);
|
||||||
|
Object accessTokenValue = redisTemplate.opsForValue().get(userAccessKey);
|
||||||
|
Optional.of(accessTokenValue)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.ifPresent(accessToken -> redisTemplate.delete(formatTokenKey(accessToken)));
|
||||||
|
// 无论是否存在访问令牌映射,都尝试删除 userAccessKey
|
||||||
|
redisTemplate.delete(userAccessKey);
|
||||||
|
|
||||||
|
// 2. 删除刷新令牌相关
|
||||||
|
String userRefreshKey = StrUtil.format(RedisConstants.Auth.USER_REFRESH_TOKEN, userId);
|
||||||
|
Object refreshTokenValue = redisTemplate.opsForValue().get(userRefreshKey);
|
||||||
|
Optional.of(refreshTokenValue)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.ifPresent(refreshToken ->
|
||||||
|
redisTemplate.delete(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken))
|
||||||
|
);
|
||||||
|
// 同样清理 userRefreshKey 本身
|
||||||
|
redisTemplate.delete(userRefreshKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将访问令牌和刷新令牌存储至 Redis
|
* 将访问令牌和刷新令牌存储至 Redis
|
||||||
*
|
*
|
||||||
@@ -218,10 +236,10 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userId);
|
String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userId);
|
||||||
// 单设备登录控制,删除旧的访问令牌
|
// 单设备登录控制,删除旧的访问令牌
|
||||||
if (!allowMultiLogin) {
|
if (!allowMultiLogin) {
|
||||||
String oldAccessToken = (String) redisTemplate.opsForValue().get(userAccessKey);
|
Object oldAccessTokenValue = redisTemplate.opsForValue().get(userAccessKey);
|
||||||
if (oldAccessToken != null) {
|
Optional.of(oldAccessTokenValue)
|
||||||
redisTemplate.delete(formatTokenKey(oldAccessToken));
|
.map(String.class::cast)
|
||||||
}
|
.ifPresent(oldAccessToken -> redisTemplate.delete(formatTokenKey(oldAccessToken)));
|
||||||
}
|
}
|
||||||
// 存储访问令牌映射(用户ID -> 访问令牌),用于单设备登录控制删除旧的访问令牌和刷新令牌时删除旧令牌
|
// 存储访问令牌映射(用户ID -> 访问令牌),用于单设备登录控制删除旧的访问令牌和刷新令牌时删除旧令牌
|
||||||
setRedisValue(userAccessKey, accessToken, securityProperties.getSession().getAccessTokenTimeToLive());
|
setRedisValue(userAccessKey, accessToken, securityProperties.getSession().getAccessTokenTimeToLive());
|
||||||
|
|||||||
@@ -64,5 +64,13 @@ public interface TokenManager {
|
|||||||
// throw new UnsupportedOperationException("Not implemented");
|
// throw new UnsupportedOperationException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使指定用户的所有会话失效
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
default void invalidateUserSessions(Long userId) {
|
||||||
|
// 默认空实现,由具体 TokenManager 决定是否支持按用户下线
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,24 +195,24 @@ public class UserController {
|
|||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "重置用户密码")
|
@Operation(summary = "重置指定用户密码")
|
||||||
@PutMapping(value = "/{userId}/password/reset")
|
@PutMapping(value = "/{userId}/password/reset")
|
||||||
@PreAuthorize("@ss.hasPerm('sys:user:reset-password')")
|
@PreAuthorize("@ss.hasPerm('sys:user:reset-password')")
|
||||||
public Result<?> resetPassword(
|
public Result<?> resetUserPassword(
|
||||||
@Parameter(description = "用户ID") @PathVariable Long userId,
|
@Parameter(description = "用户ID") @PathVariable Long userId,
|
||||||
@RequestParam String password
|
@RequestParam String password
|
||||||
) {
|
) {
|
||||||
boolean result = userService.resetPassword(userId, password);
|
boolean result = userService.resetUserPassword(userId, password);
|
||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "修改密码")
|
@Operation(summary = "当前用户修改密码")
|
||||||
@PutMapping(value = "/password")
|
@PutMapping(value = "/password")
|
||||||
public Result<?> changePassword(
|
public Result<?> changeCurrentUserPassword(
|
||||||
@RequestBody PasswordUpdateForm data
|
@RequestBody PasswordUpdateForm data
|
||||||
) {
|
) {
|
||||||
Long currUserId = SecurityUtils.getUserId();
|
Long currUserId = SecurityUtils.getUserId();
|
||||||
boolean result = userService.changePassword(currUserId, data);
|
boolean result = userService.changeUserPassword(currUserId, data);
|
||||||
return Result.judge(result);
|
return Result.judge(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,22 +107,22 @@ public interface UserService extends IService<User> {
|
|||||||
boolean updateUserProfile(UserProfileForm formData);
|
boolean updateUserProfile(UserProfileForm formData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改用户密码
|
* 修改指定用户密码
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param data 修改密码表单数据
|
* @param data 修改密码表单数据
|
||||||
* @return {@link Boolean} 是否修改成功
|
* @return {@link Boolean} 是否修改成功
|
||||||
*/
|
*/
|
||||||
boolean changePassword(Long userId, PasswordUpdateForm data);
|
boolean changeUserPassword(Long userId, PasswordUpdateForm data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置用户密码
|
* 重置指定用户密码
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param password 重置后的密码
|
* @param password 重置后的密码
|
||||||
* @return {@link Boolean} 是否重置成功
|
* @return {@link Boolean} 是否重置成功
|
||||||
*/
|
*/
|
||||||
boolean resetPassword(Long userId, String password);
|
boolean resetUserPassword(Long userId, String password);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送短信验证码(绑定或更换手机号)
|
* 发送短信验证码(绑定或更换手机号)
|
||||||
|
|||||||
@@ -72,11 +72,9 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> i
|
|||||||
.in(UserRole::getRoleId, removedRoles));
|
.in(UserRole::getRoleId, removedRoles));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当权限变更时清除登录态
|
// 当权限变更时清除被修改用户的登录态
|
||||||
if (rolesChanged) {
|
if (rolesChanged) {
|
||||||
// 获取用户所有有效token(根据实际token存储实现)
|
tokenManager.invalidateUserSessions(userId);
|
||||||
String accessToken = SecurityUtils.getTokenFromRequest();
|
|
||||||
tokenManager.invalidateToken(accessToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -485,14 +485,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改用户密码
|
* 修改指定用户密码
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param data 密码修改表单数据
|
* @param data 密码修改表单数据
|
||||||
* @return true|false
|
* @return true|false
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean changePassword(Long userId, PasswordUpdateForm data) {
|
public boolean changeUserPassword(Long userId, PasswordUpdateForm data) {
|
||||||
|
|
||||||
User user = this.getById(userId);
|
User user = this.getById(userId);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@@ -522,26 +522,30 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
// 加入黑名单,重新登录
|
// 密码变更后,使当前用户的所有会话失效,强制重新登录
|
||||||
String accessToken = SecurityUtils.getTokenFromRequest();
|
tokenManager.invalidateUserSessions(userId);
|
||||||
tokenManager.invalidateToken(accessToken);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置密码
|
* 重置指定用户密码
|
||||||
*
|
*
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param password 密码重置表单数据
|
* @param password 密码重置表单数据
|
||||||
* @return true|false
|
* @return true|false
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean resetPassword(Long userId, String password) {
|
public boolean resetUserPassword(Long userId, String password) {
|
||||||
return this.update(new LambdaUpdateWrapper<User>()
|
boolean result = this.update(new LambdaUpdateWrapper<User>()
|
||||||
.eq(User::getId, userId)
|
.eq(User::getId, userId)
|
||||||
.set(User::getPassword, passwordEncoder.encode(password))
|
.set(User::getPassword, passwordEncoder.encode(password))
|
||||||
);
|
);
|
||||||
|
if (result) {
|
||||||
|
// 管理员重置用户密码后,使该用户的所有会话失效
|
||||||
|
tokenManager.invalidateUserSessions(userId);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user