mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	实现管理后台登出时,删除 oauth 令牌
This commit is contained in:
		| @ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.security.core.filter; | |||||||
|  |  | ||||||
| import cn.hutool.core.util.ObjectUtil; | import cn.hutool.core.util.ObjectUtil; | ||||||
| import cn.hutool.core.util.StrUtil; | 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.pojo.CommonResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||||||
| import cn.iocoder.yudao.framework.security.config.SecurityProperties; | import cn.iocoder.yudao.framework.security.config.SecurityProperties; | ||||||
| @ -44,23 +45,14 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { | |||||||
|         if (StrUtil.isNotEmpty(token)) { |         if (StrUtil.isNotEmpty(token)) { | ||||||
|             Integer userType = WebFrameworkUtils.getLoginUserType(request); |             Integer userType = WebFrameworkUtils.getLoginUserType(request); | ||||||
|             try { |             try { | ||||||
|                 // 验证 token 有效性 |                 // 1.1 基于 token 构建登录用户 | ||||||
|                 OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token); |                 LoginUser loginUser = buildLoginUserByToken(token, userType); | ||||||
|                 if (accessToken != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) { // 用户类型不匹配,无权限 |                 // 1.2 模拟 Login 功能,方便日常开发调试 | ||||||
|                     throw new AccessDeniedException("错误的用户类型"); |  | ||||||
|                 } |  | ||||||
|                 LoginUser loginUser = null; |  | ||||||
|                 if (accessToken != null) { // 如果不为空,说明认证通过,则转换成登录用户 |  | ||||||
|                     loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) |  | ||||||
|                             .setTenantId(accessToken.getTenantId()); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // 模拟 Login 功能,方便日常开发调试 |  | ||||||
|                 if (loginUser == null) { |                 if (loginUser == null) { | ||||||
|                     loginUser = mockLoginUser(request, token, userType); |                     loginUser = mockLoginUser(request, token, userType); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // 设置当前用户 |                 // 2. 设置当前用户 | ||||||
|                 if (loginUser != null) { |                 if (loginUser != null) { | ||||||
|                     SecurityFrameworkUtils.setLoginUser(loginUser, request); |                     SecurityFrameworkUtils.setLoginUser(loginUser, request); | ||||||
|                 } |                 } | ||||||
| @ -75,6 +67,25 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { | |||||||
|         chain.doFilter(request, response); |         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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 模拟登录用户,方便日常开发调试 |      * 模拟登录用户,方便日常开发调试 | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ public enum LoginLogTypeEnum { | |||||||
|     LOGIN_SMS(104), // 使用短信登陆 |     LOGIN_SMS(104), // 使用短信登陆 | ||||||
|  |  | ||||||
|     LOGOUT_SELF(200),  // 自己主动登出 |     LOGOUT_SELF(200),  // 自己主动登出 | ||||||
|     LOGOUT_TIMEOUT(201), // 超时登出 |  | ||||||
|     LOGOUT_DELETE(202), // 强制退出 |     LOGOUT_DELETE(202), // 强制退出 | ||||||
|     ; |     ; | ||||||
|  |  | ||||||
|  | |||||||
| @ -33,8 +33,6 @@ import java.util.List; | |||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 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 cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||||||
| import static java.util.Collections.singleton; | import static java.util.Collections.singleton; | ||||||
|  |  | ||||||
| @ -63,7 +61,7 @@ public class AuthController { | |||||||
|     @ApiOperation("使用账号密码登录") |     @ApiOperation("使用账号密码登录") | ||||||
|     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 |     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||||||
|     public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) { |     public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) { | ||||||
|         String token = authService.login(reqVO, getClientIP(), getUserAgent()); |         String token = authService.login(reqVO); | ||||||
|         return success(AuthLoginRespVO.builder().token(token).build()); |         return success(AuthLoginRespVO.builder().token(token).build()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -116,7 +114,7 @@ public class AuthController { | |||||||
|     @ApiOperation("使用短信验证码登录") |     @ApiOperation("使用短信验证码登录") | ||||||
|     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 |     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||||||
|     public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { |     public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) { | ||||||
|         String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent()); |         String token = authService.smsLogin(reqVO); | ||||||
|         // 返回结果 |         // 返回结果 | ||||||
|         return success(AuthLoginRespVO.builder().token(token).build()); |         return success(AuthLoginRespVO.builder().token(token).build()); | ||||||
|     } |     } | ||||||
| @ -146,7 +144,7 @@ public class AuthController { | |||||||
|     @ApiOperation("社交快捷登录,使用 code 授权码") |     @ApiOperation("社交快捷登录,使用 code 授权码") | ||||||
|     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 |     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||||||
|     public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { |     public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialQuickLoginReqVO reqVO) { | ||||||
|         String token = authService.socialQuickLogin(reqVO, getClientIP(), getUserAgent()); |         String token = authService.socialQuickLogin(reqVO); | ||||||
|         return success(AuthLoginRespVO.builder().token(token).build()); |         return success(AuthLoginRespVO.builder().token(token).build()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -154,7 +152,7 @@ public class AuthController { | |||||||
|     @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码") |     @ApiOperation("社交绑定登录,使用 code 授权码 + 账号密码") | ||||||
|     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 |     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||||||
|     public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { |     public CommonResult<AuthLoginRespVO> socialBindLogin(@RequestBody @Valid AuthSocialBindLoginReqVO reqVO) { | ||||||
|         String token = authService.socialBindLogin(reqVO, getClientIP(), getUserAgent()); |         String token = authService.socialBindLogin(reqVO); | ||||||
|         return success(AuthLoginRespVO.builder().token(token).build()); |         return success(AuthLoginRespVO.builder().token(token).build()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,16 +1,16 @@ | |||||||
| package cn.iocoder.yudao.module.system.dal.mysql.auth; | 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 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 com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> { | public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> { | ||||||
|  |  | ||||||
|     default int deleteByUserIdAndUserType(Integer userId, Integer userType) { |     default int deleteByRefreshToken(String refreshToken) { | ||||||
|         return delete(new QueryWrapper<OAuth2RefreshTokenDO>() |         return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>() | ||||||
|                 .eq("user_id", userId).eq("user_type", userType)); |                 .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @ -17,11 +17,9 @@ public interface AdminAuthService { | |||||||
|      * 账号登录 |      * 账号登录 | ||||||
|      * |      * | ||||||
|      * @param reqVO 登录信息 |      * @param reqVO 登录信息 | ||||||
|      * @param userIp 用户 IP |  | ||||||
|      * @param userAgent 用户 UA |  | ||||||
|      * @return 身份令牌,使用 JWT 方式 |      * @return 身份令牌,使用 JWT 方式 | ||||||
|      */ |      */ | ||||||
|     String login(@Valid AuthLoginReqVO reqVO, String userIp, String userAgent); |     String login(@Valid AuthLoginReqVO reqVO); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 基于 token 退出登录 |      * 基于 token 退出登录 | ||||||
| @ -41,21 +39,17 @@ public interface AdminAuthService { | |||||||
|      * 短信登录 |      * 短信登录 | ||||||
|      * |      * | ||||||
|      * @param reqVO 登录信息 |      * @param reqVO 登录信息 | ||||||
|      * @param userIp 用户 IP |  | ||||||
|      * @param userAgent 用户 UA |  | ||||||
|      * @return 身份令牌,使用 JWT 方式 |      * @return 身份令牌,使用 JWT 方式 | ||||||
|      */ |      */ | ||||||
|     String smsLogin(AuthSmsLoginReqVO reqVO, String userIp, String userAgent) ; |     String smsLogin(AuthSmsLoginReqVO reqVO) ; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 社交快捷登录,使用 code 授权码 |      * 社交快捷登录,使用 code 授权码 | ||||||
|      * |      * | ||||||
|      * @param reqVO 登录信息 |      * @param reqVO 登录信息 | ||||||
|      * @param userIp 用户 IP |  | ||||||
|      * @param userAgent 用户 UA |  | ||||||
|      * @return 身份令牌,使用 JWT 方式 |      * @return 身份令牌,使用 JWT 方式 | ||||||
|      */ |      */ | ||||||
|     String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent); |     String socialQuickLogin(@Valid AuthSocialQuickLoginReqVO reqVO); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 社交绑定登录,使用 code 授权码 + 账号密码 |      * 社交绑定登录,使用 code 授权码 + 账号密码 | ||||||
| @ -65,6 +59,6 @@ public interface AdminAuthService { | |||||||
|      * @param userAgent 用户 UA |      * @param userAgent 用户 UA | ||||||
|      * @return 身份令牌,使用 JWT 方式 |      * @return 身份令牌,使用 JWT 方式 | ||||||
|      */ |      */ | ||||||
|     String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO, String userIp, String userAgent); |     String socialBindLogin(@Valid AuthSocialBindLoginReqVO reqVO); | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -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.monitor.TracerUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||||||
| import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; | 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.logger.dto.LoginLogCreateReqDTO; | ||||||
| import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; | 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.controller.admin.auth.vo.auth.*; | ||||||
| import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; | 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.dal.dataobject.user.AdminUserDO; | ||||||
| import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; | ||||||
| import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; | import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; | ||||||
| @ -47,8 +47,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|     @Resource |     @Resource | ||||||
|     private LoginLogService loginLogService; |     private LoginLogService loginLogService; | ||||||
|     @Resource |     @Resource | ||||||
|     private UserSessionService userSessionService; |  | ||||||
|     @Resource |  | ||||||
|     private OAuth2TokenService oauth2TokenService; |     private OAuth2TokenService oauth2TokenService; | ||||||
|     @Resource |     @Resource | ||||||
|     private SocialUserService socialUserService; |     private SocialUserService socialUserService; | ||||||
| @ -60,16 +58,15 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|     private SmsCodeApi smsCodeApi; |     private SmsCodeApi smsCodeApi; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public String login(AuthLoginReqVO reqVO, String userIp, String userAgent) { |     public String login(AuthLoginReqVO reqVO) { | ||||||
|         // 判断验证码是否正确 |         // 判断验证码是否正确 | ||||||
|         verifyCaptcha(reqVO); |         verifyCaptcha(reqVO); | ||||||
|  |  | ||||||
|         // 使用账号密码,进行登录 |         // 使用账号密码,进行登录 | ||||||
|         LoginUser loginUser = login0(reqVO.getUsername(), reqVO.getPassword()); |         AdminUserDO user = login0(reqVO.getUsername(), reqVO.getPassword()); | ||||||
|  |  | ||||||
|         // 缓存登陆用户到 Redis 中,返回 Token 令牌 |         // 创建 Token 令牌,记录登录日志 | ||||||
|         return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), |         return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); | ||||||
|                 LoginLogTypeEnum.LOGIN_USERNAME, userIp, userAgent); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @ -83,9 +80,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @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()); |         AdminUserDO user = userService.getUserByMobile(reqVO.getMobile()); | ||||||
| @ -93,12 +90,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|             throw exception(USER_NOT_EXISTS); |             throw exception(USER_NOT_EXISTS); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 创建 LoginUser 对象 |  | ||||||
|         LoginUser loginUser = buildLoginUser(user); |  | ||||||
|  |  | ||||||
|         // 缓存登陆用户到 Redis 中,返回 sessionId 编号 |         // 缓存登陆用户到 Redis 中,返回 sessionId 编号 | ||||||
|         return createUserSessionAfterLoginSuccess(loginUser, reqVO.getMobile(), |         return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE); | ||||||
|                 LoginLogTypeEnum.LOGIN_MOBILE, userIp, userAgent); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @VisibleForTesting |     @VisibleForTesting | ||||||
| @ -128,7 +121,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @VisibleForTesting |     @VisibleForTesting | ||||||
|     LoginUser login0(String username, String password) { |     AdminUserDO login0(String username, String password) { | ||||||
|         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; |         final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; | ||||||
|         // 校验账号是否存在 |         // 校验账号是否存在 | ||||||
|         AdminUserDO user = userService.getUserByUsername(username); |         AdminUserDO user = userService.getUserByUsername(username); | ||||||
| @ -145,9 +138,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|             createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); |             createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED); | ||||||
|             throw exception(AUTH_LOGIN_USER_DISABLED); |             throw exception(AUTH_LOGIN_USER_DISABLED); | ||||||
|         } |         } | ||||||
|  |         return user; | ||||||
|         // 构建 User 对象 |  | ||||||
|         return buildLoginUser(user); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void createLoginLog(Long userId, String username, |     private void createLoginLog(Long userId, String username, | ||||||
| @ -170,7 +161,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO, String userIp, String userAgent) { |     public String socialQuickLogin(AuthSocialQuickLoginReqVO reqVO) { | ||||||
|         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 |         // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 | ||||||
|         Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), |         Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(), | ||||||
|                 reqVO.getCode(), reqVO.getState()); |                 reqVO.getCode(), reqVO.getState()); | ||||||
| @ -178,56 +169,46 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|             throw exception(AUTH_THIRD_LOGIN_NOT_BIND); |             throw exception(AUTH_THIRD_LOGIN_NOT_BIND); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 自动登录 |         // 获得用户 | ||||||
|         AdminUserDO user = userService.getUser(userId); |         AdminUserDO user = userService.getUser(userId); | ||||||
|         if (user == null) { |         if (user == null) { | ||||||
|             throw exception(USER_NOT_EXISTS); |             throw exception(USER_NOT_EXISTS); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 创建 LoginUser 对象 |         // 创建 Token 令牌,记录登录日志 | ||||||
|         LoginUser loginUser = buildLoginUser(user); |         return createTokenAfterLoginSuccess(user.getId(), null, LoginLogTypeEnum.LOGIN_SOCIAL); | ||||||
|  |  | ||||||
|         // 缓存登录用户到 Redis 中,返回 Token 令牌 |  | ||||||
|         return createUserSessionAfterLoginSuccess(loginUser, null, |  | ||||||
|                 LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @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 令牌 |         // 创建 Token 令牌,记录登录日志 | ||||||
|         return createUserSessionAfterLoginSuccess(loginUser, reqVO.getUsername(), |         return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); | ||||||
|                 LoginLogTypeEnum.LOGIN_SOCIAL, userIp, userAgent); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private String createUserSessionAfterLoginSuccess(LoginUser loginUser, String username, |     private String createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) { | ||||||
|                                                       LoginLogTypeEnum logType, String userIp, String userAgent) { |  | ||||||
|         // 插入登陆日志 |         // 插入登陆日志 | ||||||
|         createLoginLog(loginUser.getId(), username, logType, LoginResultEnum.SUCCESS); |         createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS); | ||||||
|         // 创建访问令牌 |         // 创建访问令牌 | ||||||
|         // TODO userIp、userAgent |  | ||||||
|         // TODO clientId |         // TODO clientId | ||||||
|         return oauth2TokenService.createAccessToken(loginUser.getId(), getUserType().getValue(), 1L) |         return oauth2TokenService.createAccessToken(userId, getUserType().getValue(), 1L) | ||||||
|                 .getAccessToken(); |                 .getAccessToken(); | ||||||
| //        return userSessionService.createUserSession(loginUser, userIp, userAgent); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void logout(String token) { |     public void logout(String token) { | ||||||
|         // 查询用户信息 |         // 删除访问令牌 | ||||||
|         LoginUser loginUser = userSessionService.getLoginUser(token); |         OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token); | ||||||
|         if (loginUser == null) { |         if (accessTokenDO == null) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         // 删除 session |         // 删除成功,则记录登出日志 | ||||||
|         userSessionService.deleteUserSession(token); |         createLogoutLog(accessTokenDO.getUserId()); | ||||||
|         // 记录登出日志 |  | ||||||
|         createLogoutLog(loginUser.getId()); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void createLogoutLog(Long userId) { |     private void createLogoutLog(Long userId) { | ||||||
| @ -243,10 +224,6 @@ public class AdminAuthServiceImpl implements AdminAuthService { | |||||||
|         loginLogService.createLoginLog(reqDTO); |         loginLogService.createLoginLog(reqDTO); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private LoginUser buildLoginUser(AdminUserDO user) { |  | ||||||
|         return AuthConvert.INSTANCE.convert(user).setUserType(getUserType().getValue()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private String getUsername(Long userId) { |     private String getUsername(Long userId) { | ||||||
|         if (userId == null) { |         if (userId == null) { | ||||||
|             return null; |             return null; | ||||||
|  | |||||||
| @ -59,8 +59,8 @@ public interface OAuth2TokenService { | |||||||
|      * 参考 DefaultTokenServices 的 revokeToken 方法 |      * 参考 DefaultTokenServices 的 revokeToken 方法 | ||||||
|      * |      * | ||||||
|      * @param accessToken 刷新令牌 |      * @param accessToken 刷新令牌 | ||||||
|      * @return 是否移除到 |      * @return 访问令牌的信息 | ||||||
|      */ |      */ | ||||||
|     boolean removeAccessToken(String accessToken); |     OAuth2AccessTokenDO removeAccessToken(String accessToken); | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -85,23 +85,19 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean removeAccessToken(String accessToken) { |     public OAuth2AccessTokenDO removeAccessToken(String accessToken) { | ||||||
|         return false; |         // 删除访问令牌 | ||||||
|  |         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 | //    @Override | ||||||
| //    @Transactional | //    @Transactional | ||||||
| @ -124,20 +120,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | |||||||
| //        OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); | //        OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(refreshTokenDO, refreshAccessTokenDTO.getCreateIp()); | ||||||
| //        // 返回访问令牌 | //        // 返回访问令牌 | ||||||
| //        return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); | //        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) { |     private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { | ||||||
| @ -158,19 +140,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { | |||||||
|         return refreshToken; |         return refreshToken; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| //    /** |  | ||||||
| //     * 删除 accessToken 的 MySQL 与 Redis 的数据 |  | ||||||
| //     * |  | ||||||
| //     * @param accessToken 访问令牌 |  | ||||||
| //     */ |  | ||||||
| //    private void deleteOAuth2AccessToken(String accessToken) { |  | ||||||
| //        // 删除 MySQL |  | ||||||
| //        oauth2AccessTokenMapper.deleteById(accessToken); |  | ||||||
| //        // 删除 Redis |  | ||||||
| //        oauth2AccessTokenRedisDAO.delete(accessToken); |  | ||||||
| //    } |  | ||||||
| // |  | ||||||
|     private static String generateAccessToken() { |     private static String generateAccessToken() { | ||||||
|         return IdUtil.fastSimpleUUID(); |         return IdUtil.fastSimpleUUID(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,9 +1,8 @@ | |||||||
| package cn.iocoder.yudao.module.system.service.auth; | 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.controller.admin.auth.vo.session.UserSessionPageReqVO; | ||||||
| import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; | import cn.iocoder.yudao.module.system.dal.dataobject.auth.UserSessionDO; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 在线用户 Session Service 接口 |  * 在线用户 Session Service 接口 | ||||||
| @ -20,31 +19,6 @@ public interface UserSessionService { | |||||||
|      */ |      */ | ||||||
|     PageResult<UserSessionDO> getUserSessionPage(UserSessionPageReqVO reqVO); |     PageResult<UserSessionDO> 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 |      * 删除在线用户 Session | ||||||
|      * |      * | ||||||
| @ -59,19 +33,4 @@ public interface UserSessionService { | |||||||
|      */ |      */ | ||||||
|     void deleteUserSession(Long id); |     void deleteUserSession(Long id); | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获得 Token 对应的在线用户 |  | ||||||
|      * |  | ||||||
|      * @param token 令牌 |  | ||||||
|      * @return 在线用户 |  | ||||||
|      */ |  | ||||||
|     LoginUser getLoginUser(String token); |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 获得 Session 超时时间,单位:毫秒 |  | ||||||
|      * |  | ||||||
|      * @return 超时时间 |  | ||||||
|      */ |  | ||||||
|     Long getSessionTimeoutMillis(); |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,12 +1,10 @@ | |||||||
| package cn.iocoder.yudao.module.system.service.auth; | package cn.iocoder.yudao.module.system.service.auth; | ||||||
|  |  | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.util.IdUtil; |  | ||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||||
| import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; | import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; | ||||||
| import cn.iocoder.yudao.framework.security.config.SecurityProperties; | 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.api.logger.dto.LoginLogCreateReqDTO; | ||||||
| import cn.iocoder.yudao.module.system.controller.admin.auth.vo.session.UserSessionPageReqVO; | 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.module.system.dal.dataobject.auth.UserSessionDO; | ||||||
| @ -21,12 +19,9 @@ import lombok.extern.slf4j.Slf4j; | |||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
| import java.time.Duration; |  | ||||||
| import java.util.Collection; | 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.collection.CollectionUtils.convertSet; | ||||||
| import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 在线用户 Session Service 实现类 |  * 在线用户 Session Service 实现类 | ||||||
| @ -64,29 +59,6 @@ public class UserSessionServiceImpl implements UserSessionService { | |||||||
|         return userSessionMapper.selectPage(reqVO, userIds); |         return userSessionMapper.selectPage(reqVO, userIds); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public long deleteTimeoutSession() { |  | ||||||
|         // 获取 db 里已经超时的用户列表 |  | ||||||
|         List<UserSessionDO> 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) { |     private void createLogoutLog(UserSessionDO session, LoginLogTypeEnum type) { | ||||||
|         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); |         LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); | ||||||
|         reqDTO.setLogType(type.getType()); |         reqDTO.setLogType(type.getType()); | ||||||
| @ -100,28 +72,6 @@ public class UserSessionServiceImpl implements UserSessionService { | |||||||
|         loginLogService.createLoginLog(reqDTO); |         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 |     @Override | ||||||
|     public void deleteUserSession(String token) { |     public void deleteUserSession(String token) { | ||||||
|         // 删除 Redis 缓存 |         // 删除 Redis 缓存 | ||||||
| @ -145,23 +95,4 @@ public class UserSessionServiceImpl implements UserSessionService { | |||||||
|         createLogoutLog(session, LoginLogTypeEnum.LOGOUT_DELETE); |         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(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | |||||||
|         when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true); |         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); |         assertPojoEquals(user, loginUser); | ||||||
|     } |     } | ||||||
| @ -182,8 +182,6 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | |||||||
|     @Test |     @Test | ||||||
|     public void testLogin_success() { |     public void testLogin_success() { | ||||||
|         // 准备参数 |         // 准备参数 | ||||||
|         String userIp = randomString(); |  | ||||||
|         String userAgent = randomString(); |  | ||||||
|         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> |         AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> | ||||||
|                 o.setUsername("test_username").setPassword("test_password")); |                 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); |         when(userService.isPasswordMatch(eq("test_password"), eq(user.getPassword()))).thenReturn(true); | ||||||
|         // mock 缓存登录用户到 Redis |         // mock 缓存登录用户到 Redis | ||||||
|         String token = randomString(); |         String token = randomString(); | ||||||
|         when(userSessionService.createUserSession(argThat(argument -> { | //        when(userSessionService.createUserSession(argThat(argument -> { | ||||||
|             AssertUtils.assertPojoEquals(user, argument); | //            AssertUtils.assertPojoEquals(user, argument); | ||||||
|             return true; | //            return true; | ||||||
|         }), eq(userIp), eq(userAgent))).thenReturn(token); | //        }), eq(userIp), eq(userAgent))).thenReturn(token); | ||||||
|  |         // TODO 芋艿:oauth2 | ||||||
|  |  | ||||||
|         // 调用, 并断言异常 |         // 调用, 并断言异常 | ||||||
|         String result = authService.login(reqVO, userIp, userAgent); |         String result = authService.login(reqVO); | ||||||
|         assertEquals(token, result); |         assertEquals(token, result); | ||||||
|         // 校验调用参数 |         // 校验调用参数 | ||||||
|         verify(loginLogService).createLoginLog( |         verify(loginLogService).createLoginLog( | ||||||
| @ -219,7 +218,8 @@ public class AuthServiceImplTest extends BaseDbUnitTest { | |||||||
|         String token = randomString(); |         String token = randomString(); | ||||||
|         LoginUser loginUser = randomPojo(LoginUser.class); |         LoginUser loginUser = randomPojo(LoginUser.class); | ||||||
|         // mock |         // mock | ||||||
|         when(userSessionService.getLoginUser(token)).thenReturn(loginUser); | //        when(userSessionService.getLoginUser(token)).thenReturn(loginUser); | ||||||
|  |         // TODO @芋艿:oauth2 | ||||||
|         // 调用 |         // 调用 | ||||||
|         authService.logout(token); |         authService.logout(token); | ||||||
|         // 校验调用参数 |         // 校验调用参数 | ||||||
|  | |||||||
| @ -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.CommonStatusEnum; | ||||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | 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.common.util.object.ObjectUtils; | ||||||
| import cn.iocoder.yudao.framework.security.config.SecurityProperties; | import cn.iocoder.yudao.framework.security.config.SecurityProperties; | ||||||
| import cn.iocoder.yudao.framework.security.core.LoginUser; | 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.mysql.auth.UserSessionMapper; | ||||||
| import cn.iocoder.yudao.module.system.dal.redis.auth.LoginUserRedisDAO; | 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.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.logger.LoginLogService; | ||||||
| import cn.iocoder.yudao.module.system.service.user.AdminUserService; | import cn.iocoder.yudao.module.system.service.user.AdminUserService; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| @ -25,17 +22,15 @@ import org.springframework.context.annotation.Import; | |||||||
|  |  | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| import java.util.Calendar; |  | ||||||
|  |  | ||||||
| import static cn.hutool.core.util.RandomUtil.randomEle; | 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.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 java.util.Collections.singletonList; | ||||||
| import static org.junit.jupiter.api.Assertions.*; | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
| import static org.mockito.ArgumentMatchers.argThat; | import static org.junit.jupiter.api.Assertions.assertNull; | ||||||
| import static org.mockito.ArgumentMatchers.eq; | import static org.mockito.ArgumentMatchers.eq; | ||||||
| import static org.mockito.Mockito.verify; |  | ||||||
| import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -100,112 +95,6 @@ public class UserSessionServiceImplTest extends BaseDbAndRedisUnitTest { | |||||||
|         assertPojoEquals(dbSession, pageResult.getList().get(0)); |         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 |     @Test | ||||||
|     public void testDeleteUserSession_Token() { |     public void testDeleteUserSession_Token() { | ||||||
|         // 准备参数 |         // 准备参数 | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV