From 5ea9cc3cd796db0eede69e792d842b7754ffde34 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 9 May 2022 13:29:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E7=99=BB=E5=87=BA=E6=97=B6=EF=BC=8C=E5=88=A0=E9=99=A4?= =?UTF-8?q?=20oauth=20=E4=BB=A4=E7=89=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/TokenAuthenticationFilter.java | 37 ++++-- .../system/enums/logger/LoginLogTypeEnum.java | 1 - .../controller/admin/auth/AuthController.java | 10 +- .../mysql/auth/OAuth2RefreshTokenMapper.java | 8 +- .../job/auth/UserSessionTimeoutJob.java | 32 ----- .../system/service/auth/AdminAuthService.java | 14 +-- .../service/auth/AdminAuthServiceImpl.java | 77 ++++-------- .../service/auth/OAuth2TokenService.java | 4 +- .../service/auth/OAuth2TokenServiceImpl.java | 53 ++------ .../service/auth/UserSessionService.java | 43 +------ .../service/auth/UserSessionServiceImpl.java | 69 ---------- .../service/auth/AuthServiceImplTest.java | 18 +-- .../auth/UserSessionServiceImplTest.java | 119 +----------------- 13 files changed, 90 insertions(+), 395 deletions(-) delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java index 75caa59ff..204b03fa0 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.security.core.filter; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; @@ -44,23 +45,14 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { if (StrUtil.isNotEmpty(token)) { Integer userType = WebFrameworkUtils.getLoginUserType(request); try { - // 验证 token 有效性 - OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); - if (accessToken != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 用户类型不匹配,无权限 - throw new AccessDeniedException("错误的用户类型"); - } - LoginUser loginUser = null; - if (accessToken != null) { // 如果不为空,说明认证通过,则转换成登录用户 - loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) - .setTenantId(accessToken.getTenantId()); - } - - // 模拟 Login 功能,方便日常开发调试 + // 1.1 基于 token 构建登录用户 + LoginUser loginUser = buildLoginUserByToken(token, userType); + // 1.2 模拟 Login 功能,方便日常开发调试 if (loginUser == null) { loginUser = mockLoginUser(request, token, userType); } - // 设置当前用户 + // 2. 设置当前用户 if (loginUser != null) { SecurityFrameworkUtils.setLoginUser(loginUser, request); } @@ -75,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { chain.doFilter(request, response); } + private LoginUser buildLoginUserByToken(String token, Integer userType) { + try { + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); + if (accessToken == null) { + return null; + } + // 用户类型不匹配,无权限 + if (ObjectUtil.notEqual(accessToken.getUserType(), userType)) { + throw new AccessDeniedException("错误的用户类型"); + } + // 构建登录用户 + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) + .setTenantId(accessToken.getTenantId()); + } catch (ServiceException serviceException) { + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 + return null; + } + } + /** * 模拟登录用户,方便日常开发调试 * diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java index ab9d0bbbd..4c51f9168 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/logger/LoginLogTypeEnum.java @@ -16,7 +16,6 @@ public enum LoginLogTypeEnum { LOGIN_SMS(104), // 使用短信登陆 LOGOUT_SELF(200), // 自己主动登出 - LOGOUT_TIMEOUT(201), // 超时登出 LOGOUT_DELETE(202), // 强制退出 ; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index ce9e58e8d..7c20e8d34 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -33,8 +33,6 @@ import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; -import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static java.util.Collections.singleton; @@ -63,7 +61,7 @@ public class AuthController { @ApiOperation("使用账号密码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult login(@RequestBody @Valid AuthLoginReqVO reqVO) { - String token = authService.login(reqVO, getClientIP(), getUserAgent()); + String token = authService.login(reqVO); return success(AuthLoginRespVO.builder().token(token).build()); } @@ -116,7 +114,7 @@ public class AuthController { @ApiOperation("使用短信验证码登录") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { - String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent()); + String token = authService.smsLogin(reqVO); // 返回结果 return success(AuthLoginRespVO.builder().token(token).build()); } @@ -146,7 +144,7 @@ public class AuthController { @ApiOperation("社交快捷登录,使用 code 授权码") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { - String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent()); + String token = authService.socialQuickLogin(reqVO); return success(AuthLoginRespVO.builder().token(token).build()); } @@ -154,7 +152,7 @@ public class AuthController { @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码") @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 public CommonResult socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { - String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent()); + String token = authService.socialBindLogin(reqVO); return success(AuthLoginRespVO.builder().token(token).build()); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java index e5206d242..145c7e9f8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/auth/OAuth2RefreshTokenMapper.java @@ -1,16 +1,16 @@ package cn.iocoder.yudao.module.system.dal.mysql.auth; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2RefreshTokenDO; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OAuth2RefreshTokenMapper extends BaseMapper { - default int deleteByUserIdAndUserType(Integer userId, Integer userType) { - return delete(new QueryWrapper() - .eq("user_id", userId).eq("user_type", userType)); + default int deleteByRefreshToken(String refreshToken) { + return delete(new LambdaQueryWrapperX() + .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java deleted file mode 100644 index b2bb523e3..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/auth/UserSessionTimeoutJob.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.system.job.auth; - -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; -import cn.iocoder.yudao.module.system.service.auth.UserSessionService; -import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; - -/** - * 用户 Session 超时 Job - * - * @author 願 - */ -@Component -@TenantJob -@Slf4j -public class UserSessionTimeoutJob implements JobHandler { - - @Resource - private UserSessionService userSessionService; - - @Override - public String execute(String param) throws Exception { - // 执行过期 - Long timeoutCount = userSessionService.deleteTimeoutSession(); - // 返回结果,记录每次的超时数量 - return String.format("移除在线会话数量为 %s 个", timeoutCount); - } - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 46855e593..dc5fa2a07 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -17,11 +17,9 @@ public interface AdminAuthService { * 账号登录 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); + String login(@Valid AuthLoginReqVO reqVO); /** * 基于 token 退出登录 @@ -41,21 +39,17 @@ public interface AdminAuthService { * 短信登录 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) ; + String smsLogin(AuthSmsLoginReqVO reqVO) ; /** * 社交快捷登录,使用 code 授权码 * * @param reqVO 登录信息 - * @param userIp 用户 IP - * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent); + String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO); /** * 社交绑定登录,使用 code 授权码 + 账号密码 @@ -65,6 +59,6 @@ public interface AdminAuthService { * @param userAgent 用户 UA * @return 身份令牌,使用 JWT 方式 */ - String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent); + String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index 69fd38e07..ac0b32c37 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -6,11 +6,11 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; -import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.auth.*; import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; +import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; @@ -47,8 +47,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Resource private LoginLogService loginLogService; @Resource - private UserSessionService userSessionService; - @Resource private OAuth2TokenService oauth2TokenService; @Resource private SocialUserService socialUserService; @@ -60,16 +58,15 @@ public class AdminAuthServiceImpl implements AdminAuthService { private SmsCodeApi smsCodeApi; @Override - public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { + public String login(AuthLoginReqVO reqVO) { // 判断验证码是否正确 verifyCaptcha(reqVO); // 使用账号密码,进行登录 - LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); + AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); - // 缓存登陆用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), - LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); } @Override @@ -83,9 +80,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) { + public String smsLogin(AuthSmsLoginReqVO reqVO) { // 校验验证码 - smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), userIp)); + smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP())); // 获得用户信息 AdminUserDO user = userService.getUserByMobile(reqVO.getMobile()); @@ -93,12 +90,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw exception(USER_NOT_EXISTS); } - // 创建 LoginUser 对象 - LoginUser loginUser = buildLoginUser(user); - // 缓存登陆用户到 Redis 中,返回 sessionId 编号 - return createUserSessionAfterLoginSuccess(loginUser, reqVO.getMobile(), - LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); + return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); } @VisibleForTesting @@ -128,7 +121,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @VisibleForTesting - LoginUser login0(String username, String password) { + AdminUserDO login0(String username, String password) { final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; // 校验账号是否存在 AdminUserDO user = userService.getUserByUsername(username); @@ -145,9 +138,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); throw exception(AUTH_LOGIN_USER_DISABLED); } - - // 构建 User 对象 - return buildLoginUser(user); + return user; } private void createLoginLog(Long userId, String username, @@ -170,7 +161,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { } @Override - public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { + public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) { // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getCode(), reqVO.getState()); @@ -178,56 +169,46 @@ public class AdminAuthServiceImpl implements AdminAuthService { throw exception(AUTH_THIRD_LOGIN_NOT_BIND); } - // 自动登录 + // 获得用户 AdminUserDO user = userService.getUser(userId); if (user == null) { throw exception(USER_NOT_EXISTS); } - // 创建 LoginUser 对象 - LoginUser loginUser = buildLoginUser(user); - - // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, null, - LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), null, LoginLogTypeEnum.LOGIN_SOCIAL); } @Override - public String socialBindLogin(AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent) { + public String socialBindLogin(AuthSocialBindLoginReqVO reqVO) { // 使用账号密码,进行登录。 - LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); + AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); // 绑定社交用户 - socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(loginUser.getId(), getUserType().getValue(), reqVO)); + socialUserService.bindSocialUser(AuthConvert.INSTANCE.convert(user.getId(), getUserType().getValue(), reqVO)); - // 缓存登录用户到 Redis 中,返回 Token 令牌 - return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), - LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); } - private String createUserSessionAfterLoginSuccess(LoginUser loginUser, String username, - LoginLogTypeEnum logType, String userIp, String userAgent) { + private String createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { // 插入登陆日志 - createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS); + createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); // 创建访问令牌 - // TODO userIp、userAgent // TODO clientId - return oauth2TokenService.createAccessToken(loginUser.getId(), getUserType().getValue(), 1L) + return oauth2TokenService.createAccessToken(userId, getUserType().getValue(), 1L) .getAccessToken(); -// return userSessionService.createUserSession(loginUser, userIp, userAgent); } @Override public void logout(String token) { - // 查询用户信息 - LoginUser loginUser = userSessionService.getLoginUser(token); - if (loginUser == null) { + // 删除访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token); + if (accessTokenDO == null) { return; } - // 删除 session - userSessionService.deleteUserSession(token); - // 记录登出日志 - createLogoutLog(loginUser.getId()); + // 删除成功,则记录登出日志 + createLogoutLog(accessTokenDO.getUserId()); } private void createLogoutLog(Long userId) { @@ -243,10 +224,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { loginLogService.createLoginLog(reqDTO); } - private LoginUser buildLoginUser(AdminUserDO user) { - return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); - } - private String getUsername(Long userId) { if (userId == null) { return null; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java index 65e2ddb6f..1811dcee0 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenService.java @@ -59,8 +59,8 @@ public interface OAuth2TokenService { * 参考 DefaultTokenServices 的 revokeToken 方法 * * @param accessToken 刷新令牌 - * @return 是否移除到 + * @return 访问令牌的信息 */ - boolean removeAccessToken(String accessToken); + OAuth2AccessTokenDO removeAccessToken(String accessToken); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java index a9940c482..1aa07c442 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/OAuth2TokenServiceImpl.java @@ -85,23 +85,19 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } @Override - public boolean removeAccessToken(String accessToken) { - return false; + public OAuth2AccessTokenDO removeAccessToken(String accessToken) { + // 删除访问令牌 + OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + if (accessTokenDO == null) { + return null; + } + oauth2AccessTokenMapper.deleteById(accessTokenDO.getId()); + oauth2AccessTokenRedisDAO.delete(accessToken); + // 删除刷新令牌 + oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken()); + return accessTokenDO; } -// @Override -// @Transactional -// public OAuth2AccessTokenRespDTO checkAccessToken(String accessToken) { -// OAuth2AccessTokenDO accessTokenDO = this.getOAuth2AccessToken(accessToken); -// if (accessTokenDO == null) { // 不存在 -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_NOT_FOUND); -// } -// if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期 -// throw ServiceExceptionUtil.exception(OAUTH2_ACCESS_TOKEN_TOKEN_EXPIRED); -// } -// // 返回访问令牌 -// return OAuth2Convert.INSTANCE.convert(accessTokenDO); -// } // @Override // @Transactional @@ -124,20 +120,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); // // 返回访问令牌 // return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); -// } -// -// @Override -// @Transactional -// public void removeToken(OAuth2RemoveTokenByUserReqDTO removeTokenDTO) { -// // 删除 Access Token -// OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByUserIdAndUserType( -// removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); -// if (accessTokenDO != null) { -// this.deleteOAuth2AccessToken(accessTokenDO.getId()); -// } -// -// // 删除 Refresh Token -// oauth2RefreshTokenMapper.deleteByUserIdAndUserType(removeTokenDTO.getUserId(), removeTokenDTO.getUserType()); // } private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { @@ -158,19 +140,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { return refreshToken; } - -// /** -// * 删除 accessToken 的 MySQL 与 Redis 的数据 -// * -// * @param accessToken 访问令牌 -// */ -// private void deleteOAuth2AccessToken(String accessToken) { -// // 删除 MySQL -// oauth2AccessTokenMapper.deleteById(accessToken); -// // 删除 Redis -// oauth2AccessTokenRedisDAO.delete(accessToken); -// } -// private static String generateAccessToken() { return IdUtil.fastSimpleUUID(); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java index 844cef250..aaf02fc7b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionService.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.system.service.auth; -import cn.iocoder.yudao.framework.security.core.LoginUser; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; -import cn.iocoder.yudao.framework.common.pojo.PageResult; /** * 在线用户 Session Service 接口 @@ -20,31 +19,6 @@ public interface UserSessionService { */ PageResult getUserSessionPage(UserSessionPageReqVO reqVO); - /** - * 移除超时的在线用户 - * - * @return {@link Long } 移出的超时用户数量 - **/ - long deleteTimeoutSession(); - - /** - * 创建在线用户 Session - * - * @param loginUser 登录用户 - * @param userIp 用户 IP - * @param userAgent 用户 UA - * @return Token 令牌 - */ - String createUserSession(LoginUser loginUser, String userIp, String userAgent); - - /** - * 刷新在线用户 Session 的更新时间 - * - * @param token 令牌 - * @param loginUser 登录用户 - */ - void refreshUserSession(String token, LoginUser loginUser); - /** * 删除在线用户 Session * @@ -59,19 +33,4 @@ public interface UserSessionService { */ void deleteUserSession(Long id); - /** - * 获得 Token 对应的在线用户 - * - * @param token 令牌 - * @return 在线用户 - */ - LoginUser getLoginUser(String token); - - /** - * 获得 Session 超时时间,单位:毫秒 - * - * @return 超时时间 - */ - Long getSessionTimeoutMillis(); - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java index 75cedcf18..b09416486 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImpl.java @@ -1,12 +1,10 @@ package cn.iocoder.yudao.module.system.service.auth; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; -import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; @@ -21,12 +19,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.time.Duration; import java.util.Collection; -import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; /** * 在线用户 Session Service 实现类 @@ -64,29 +59,6 @@ public class UserSessionServiceImpl implements UserSessionService { return userSessionMapper.selectPage(reqVO, userIds); } - @Override - public long deleteTimeoutSession() { - // 获取 db 里已经超时的用户列表 - List timeoutSessions = userSessionMapper.selectListBySessionTimoutLt(); - if (CollUtil.isEmpty(timeoutSessions)) { - return 0L; - } - - // 由于过期的用户一般不多,所以顺序遍历,进行清理 - int count = 0; - for (UserSessionDO session : timeoutSessions) { - // 基于 Redis 二次判断,同时也保证 Redis Key 的立即过期,避免延迟导致浪费内存空间 - if (loginUserRedisDAO.exists(session.getToken())) { - continue; - } - userSessionMapper.deleteById(session.getId()); - // 记录退出日志 - createLogoutLog(session, LoginLogTypeEnum.LOGOUT_TIMEOUT); - count++; - } - return count; - } - private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) { LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); reqDTO.setLogType(type.getType()); @@ -100,28 +72,6 @@ public class UserSessionServiceImpl implements UserSessionService { loginLogService.createLoginLog(reqDTO); } - @Override - public String createUserSession(LoginUser loginUser, String userIp, String userAgent) { - // 生成 Session 编号 - String token = generateToken(); - // 写入 Redis 缓存 - loginUserRedisDAO.set(token, loginUser); - // 写入 DB 中 - UserSessionDO userSession = UserSessionDO.builder().token(token) - .userId(loginUser.getId()).userType(loginUser.getUserType()) - .userIp(userIp).userAgent(userAgent).username("") - .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))) - .build(); - userSessionMapper.insert(userSession); - // 返回 Token 令牌 - return token; - } - - @Override - public void refreshUserSession(String token, LoginUser loginUser) { - - } - @Override public void deleteUserSession(String token) { // 删除 Redis 缓存 @@ -145,23 +95,4 @@ public class UserSessionServiceImpl implements UserSessionService { createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE); } - @Override - public LoginUser getLoginUser(String token) { - return loginUserRedisDAO.get(token); - } - - @Override - public Long getSessionTimeoutMillis() { - return securityProperties.getSessionTimeout().toMillis(); - } - - /** - * 生成 Token 令牌,目前采用 UUID 算法 - * - * @return Session 编号 - */ - private static String generateToken() { - return IdUtil.fastSimpleUUID(); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java index 762928f0a..134f074ce 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AuthServiceImplTest.java @@ -70,7 +70,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); // 调用 - LoginUser loginUser = authService.login0(username, password); + AdminUserDO loginUser = authService.login0(username, password); // 校验 assertPojoEquals(user, loginUser); } @@ -182,8 +182,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { @Test public void testLogin_success() { // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> o.setUsername("test_username").setPassword("test_password")); @@ -197,13 +195,14 @@ public class AuthServiceImplTest extends BaseDbUnitTest { when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); // mock 缓存登录用户到 Redis String token = randomString(); - when(userSessionService.createUserSession(argThat(argument -> { - AssertUtils.assertPojoEquals(user, argument); - return true; - }), eq(userIp), eq(userAgent))).thenReturn(token); +// when(userSessionService.createUserSession(argThat(argument -> { +// AssertUtils.assertPojoEquals(user, argument); +// return true; +// }), eq(userIp), eq(userAgent))).thenReturn(token); + // TODO 芋艿:oauth2 // 调用, 并断言异常 - String result = authService.login(reqVO, userIp, userAgent); + String result = authService.login(reqVO); assertEquals(token, result); // 校验调用参数 verify(loginLogService).createLoginLog( @@ -219,7 +218,8 @@ public class AuthServiceImplTest extends BaseDbUnitTest { String token = randomString(); LoginUser loginUser = randomPojo(LoginUser.class); // mock - when(userSessionService.getLoginUser(token)).thenReturn(loginUser); +// when(userSessionService.getLoginUser(token)).thenReturn(loginUser); + // TODO @芋艿:oauth2 // 调用 authService.logout(token); // 校验调用参数 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java index e06591cca..8daf56525 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/UserSessionServiceImplTest.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.service.auth; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.security.config.SecurityProperties; import cn.iocoder.yudao.framework.security.core.LoginUser; @@ -14,8 +13,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.auth.UserSessionMapper; import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO; import cn.iocoder.yudao.module.system.enums.common.SexEnum; -import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; -import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; import cn.iocoder.yudao.module.system.service.logger.LoginLogService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import org.junit.jupiter.api.BeforeEach; @@ -25,17 +22,15 @@ import org.springframework.context.annotation.Import; import javax.annotation.Resource; import java.time.Duration; -import java.util.Calendar; import static cn.hutool.core.util.RandomUtil.randomEle; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -100,112 +95,6 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { assertPojoEquals(dbSession, pageResult.getList().get(0)); } - @Test - public void testClearSessionTimeout_none() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setSessionTimeout(addTime(Duration.ofDays(1))); - }); - userSessionMapper.insert(userSession); - - // 调用 - long count = userSessionService.deleteTimeoutSession(); - // 断言 - assertEquals(0, count); - assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除 - } - - @Test // Redis 还存在的情况 - public void testClearSessionTimeout_exists() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1)); - }); - userSessionMapper.insert(userSession); - // mock redis 数据 - loginUserRedisDAO.set(userSession.getToken(), new LoginUser()); - - // 调用 - long count = userSessionService.deleteTimeoutSession(); - // 断言 - assertEquals(0, count); - assertPojoEquals(userSession, userSessionMapper.selectById(userSession.getId())); // 未删除 - } - - @Test - public void testClearSessionTimeout_success() { - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setSessionTimeout(DateUtils.addDate(Calendar.DAY_OF_YEAR, -1)); - }); - userSessionMapper.insert(userSession); - - // 清空超时数据 - long count = userSessionService.deleteTimeoutSession(); - // 校验 - assertEquals(1, count); - assertNull(userSessionMapper.selectById(userSession.getId())); // 已删除 - verify(loginLogService).createLoginLog(argThat(loginLog -> { - assertPojoEquals(userSession, loginLog); - assertEquals(LoginLogTypeEnum.LOGOUT_TIMEOUT.getType(), loginLog.getLogType()); - assertEquals(LoginResultEnum.SUCCESS.getResult(), loginLog.getResult()); - return true; - })); - } - - @Test - public void testCreateUserSession_success() { - // 准备参数 - String userIp = randomString(); - String userAgent = randomString(); - LoginUser loginUser = randomPojo(LoginUser.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setTenantId(0L); // 租户设置为 0,因为暂未启用多租户组件 - }); - - // 调用 - String token = userSessionService.createUserSession(loginUser, userIp, userAgent); - // 校验 UserSessionDO 记录 - UserSessionDO userSessionDO = userSessionMapper.selectOne(UserSessionDO::getToken, token); - assertPojoEquals(loginUser, userSessionDO, "id", "updateTime"); - assertEquals(token, userSessionDO.getToken()); - assertEquals(userIp, userSessionDO.getUserIp()); - assertEquals(userAgent, userSessionDO.getUserAgent()); - // 校验 LoginUser 缓存 - LoginUser redisLoginUser = loginUserRedisDAO.get(token); - assertPojoEquals(loginUser, redisLoginUser, "username", "password"); - } - - @Test - public void testCreateRefreshUserSession() { - // 准备参数 - String token = randomString(); - - // mock redis 数据 - LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setUserType(randomEle(UserTypeEnum.values()).getValue())); - loginUserRedisDAO.set(token, loginUser); - // mock db 数据 - UserSessionDO userSession = randomPojo(UserSessionDO.class, o -> { - o.setUserType(randomEle(UserTypeEnum.values()).getValue()); - o.setToken(token); - }); - userSessionMapper.insert(userSession); - - // 调用 - userSessionService.refreshUserSession(token, loginUser); - // 校验 LoginUser 缓存 - LoginUser redisLoginUser = loginUserRedisDAO.get(token); - assertPojoEquals(redisLoginUser, loginUser, "username", "password"); - // 校验 UserSessionDO 记录 - UserSessionDO updateDO = userSessionMapper.selectOne(UserSessionDO::getToken, token); -// assertEquals(updateDO.getUsername(), loginUser.getUsername()); - assertNotNull(userSession.getUpdateTime()); - assertNotNull(userSession.getSessionTimeout()); - } - @Test public void testDeleteUserSession_Token() { // 准备参数