实现单设备登录功能

This commit is contained in:
2025-06-16 16:20:20 +08:00
parent 45ce929bd4
commit 2b1bf8be00
5 changed files with 45 additions and 2 deletions

View File

@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
@@ -32,4 +33,12 @@ public interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO
.orderByDesc(OAuth2AccessTokenDO::getId));
}
default void deleteByUserId(Long userId) {
delete(Wrappers.lambdaUpdate(OAuth2AccessTokenDO.class).eq(OAuth2AccessTokenDO::getUserId, userId));
}
default List<OAuth2AccessTokenDO> selectListByUserId(Long userId) {
return selectList(OAuth2AccessTokenDO::getUserId, userId);
}
}

View File

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@@ -18,5 +19,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
}
default void deleteByUserId(Long userId) {
delete(Wrappers.lambdaUpdate(OAuth2RefreshTokenDO.class).eq(OAuth2RefreshTokenDO::getUserId, userId));
}
}

View File

@@ -40,6 +40,11 @@ public interface OAuth2TokenService {
*/
OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId);
/**
* 删除用户所有返回令牌
* @param userId 用户 Id
*/
void removeAccessTokenByUserId(Long userId);
/**
* 获得访问令牌
*

View File

@@ -8,6 +8,7 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
@@ -22,6 +23,7 @@ import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper;
import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper;
import cn.iocoder.yudao.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -31,6 +33,7 @@ import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -57,11 +60,19 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
@Lazy // 懒加载,避免循环依赖
private AdminUserService adminUserService;
@Value("${multiple-device-login}")
private Boolean multipleDeviceLoginConfig;
@Override
@Transactional(rollbackFor = Exception.class)
public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes) {
// 在 yaml multiple-device-login = True 时 删除用户上次登录令牌,实现单设备登录
if (multipleDeviceLoginConfig){
removeAccessTokenByUserId(userId);
}
OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
// 创建刷新令牌
OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes);
// 创建访问令牌
return createOAuth2AccessToken(refreshTokenDO, clientDO);
@@ -99,6 +110,18 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
return createOAuth2AccessToken(refreshTokenDO, clientDO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeAccessTokenByUserId(Long userId) {
List<OAuth2AccessTokenDO> oAuth2AccessTokenDOList = oauth2AccessTokenMapper.selectListByUserId(userId);
if (!CollectionUtils.isAnyEmpty(oAuth2AccessTokenDOList)) {
oauth2AccessTokenRedisDAO.deleteList(oAuth2AccessTokenDOList.stream().map(OAuth2AccessTokenDO::getAccessToken).collect(Collectors.toList()));
}
oauth2AccessTokenMapper.deleteByUserId(userId);
oauth2RefreshTokenMapper.deleteByUserId(userId);
}
@Override
public OAuth2AccessTokenDO getAccessToken(String accessToken) {
// 优先从 Redis 中获取
@@ -126,6 +149,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
return accessTokenDO;
}
@Override
public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);